Breadcrumb
The <x-wirekit:: component renders a navigation trail with customizable separators. It follows the WAI-ARIA Breadcrumb pattern.
Usage
The last item is always rendered as the current page (no link, aria-current="page").
With Icons
Add an icon key to any item to render a decorative glyph before its label
(handy for a leading home icon, or a category icon per crumb). The label stays
the accessible link text — the icon is aria-hidden and never reaches the
structured data.
Items
Each item can be either an associative array or a plain string:
{{-- Full form: label + href --}}
['label' => 'Products', 'href' => '/products']
{{-- Short form: label only (rendered without link) --}}
'Products'
Items without href are rendered as plain <span> (no link, no current indicator unless it's the last item). One in the middle of the trail — a navigation group with no page of its own — renders muted, like the links around it, so the emphasis stays on the page the reader is on. Its classes are the ancestor block's (see Personalization).
Only http, https, mailto, tel and relative addresses become links. An href with any other scheme, such as javascript:, is dropped, and that item renders like one without href, in the trail and in the structured data.
Separators
| Value | Character |
|---|---|
chevron (default) |
› |
slash |
/ |
arrow |
→ |
dot |
· |
| custom | any string you pass |
A long trail on a narrow screen
A trail wraps onto another line when it does not fit, and breaks only between items, so every label stays whole. On a phone a deep trail takes two or three lines instead of running off the side of the screen:
Structured Data (SEO)
The component automatically renders a Schema.org BreadcrumbList as JSON-LD. Search engines (Google, Bing) use this to display breadcrumb trails directly in search results.
<!-- Rendered automatically — no extra setup needed -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "/" },
{ "@type": "ListItem", "position": 2, "name": "Products", "item": "/products" },
{ "@type": "ListItem", "position": 3, "name": "Widget" }
]
}
</script>
Items with an href get the item property (the URL). The last item (current page) typically has no item URL, which is recommended by Google.
Google needs a URL on every entry except the last, and one entry without it is enough to make it discard the whole trail — the search result then shows a bare URL instead. So two kinds of step are handled differently in the structured data than in the visible trail:
- A step without
hrefin the middle of the trail — a navigation group with no page of its own — is shown in the trail and left out of the JSON-LD. The positions are counted over the entries that remain. - Consecutive steps with the same
hrefbecome one entry, named after the first of them.
Validate your breadcrumbs with Google's Rich Results Test to ensure they appear correctly in search results.
More than one breadcrumb on a page
The trail emits its structured data without being asked, which is right for the ordinary case
and wrong the moment a page has two breadcrumbs — a shell's top bar and one in the content body.
Pass schema="false" on the second:
{{-- 1. The shell's top bar carries the canonical trail, and its structured data. --}}
<x-wirekit::shell-bar>
<x-wirekit::breadcrumb :items="$trail" />
</x-wirekit::shell-bar>
{{-- 2. A second, visual-only copy in the body. Same trail, no competing JSON-LD. --}}
<x-wirekit::breadcrumb :items="$trail" schema="false" />
A control beside the trail
Some controls belong to the trail without being a step in it — a favorite toggle for the page
you are on, a copy-link button, a star. The actions slot renders them inside the <nav>
landmark and outside the <ol>.
That placement is the whole point. The list is mirrored one-to-one into the BreadcrumbList
structured data below, where every entry is a ListItem — a position in the trail. A button is
not a position, so a control placed inside the list would publish a crumb that leads nowhere.
The slot leaves the structured data untouched, so a trail with a control emits exactly the same JSON-LD as the same trail without one.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items |
array |
[] |
Array of items; each is ['label' => .., 'href' => .., 'icon' => ..] or a plain string |
separator |
string |
'chevron' |
'chevron', 'slash', 'arrow', 'dot', or any custom string |
schema |
bool |
true |
Emit BreadcrumbList JSON-LD for this trail. Turn it off when the page already carries a breadcrumb elsewhere — a shell's top bar plus one in the content body is the common case. A page must carry exactly one BreadcrumbList; two of them compete rather than combine, and a crawler shown two trails for one URL picks one or neither |
scope |
string|null |
null |
Scoped personalization key |
Slots
| Slot | Description |
|---|---|
actions |
A control that belongs to the trail without being a step in it. Rendered inside the <nav> and after the <ol>, so it never reaches the BreadcrumbList structured data |
Accessibility
- Outer
<nav aria-label="Breadcrumb">— the landmark - Semantic
<ol>/<li>structure — reads as an ordered list - Last item marked with
aria-current="page" - Separators marked
aria-hidden="true"so screen readers don't announce "chevron" - An empty trail, as on an application's home page, renders the landmark hidden and without a list, so nothing is announced; the landmark stays visible when the actions slot holds a control
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus to the link |
Enter |
Activate the link (follow the URL) |
Each crumb is a link with native browser keyboard semantics.
Pitfalls
- Don't link the current page in the last crumb. WAI-ARIA Authoring Practices require the trailing crumb to use
aria-current="page"and render as plain text — the component already enforces this when the last item omitshref. - Don't include more than 5 crumbs. Cognitive overhead spikes; collapse mid-path with an ellipsis crumb.
Design Tokens
| Token | Used for |
|---|---|
--font-wk-body-weight |
Item font weight |
--text-wk-sm |
Item font size |
--color-wk-text |
Current page item |
--color-wk-text-muted |
Ancestor links, and ancestors without a page |
--color-wk-text-subtle |
Separator chevron |
--color-wk-ring |
Focus ring |
--ring-wk-width |
Focus ring width |
--radius-wk-sm |
Focus ring radius |
--padding-wk-x-xs |
Item horizontal padding |
--transition-wk-duration |
Hover transition |
Personalization
These class blocks can be replaced through WireKit:::
| Block | Element |
|---|---|
nav |
The <nav> landmark around the trail |
list |
The <ol> that holds the steps |
link |
A step with an href |
ancestor |
A step without an href that is not the last one — a navigation group with no page of its own |
current |
The last step, the page the reader is on |
actions |
The wrapper around the actions slot |
Replacing ancestor restyles the unlinked steps without touching the current page:
use Pushery\WireKit\WireKit;
WireKit::personalize('breadcrumb', [
'ancestor' => 'text-[color:var(--color-wk-text-subtle)] italic',
]);
Config Defaults
The defaults live in config/ under components.. Override them globally:
'components' => [
'breadcrumb' => ['separator' => 'slash'], // change default separator
],
Further Reading
- WAI-ARIA Breadcrumb Pattern — the authoring pattern this component implements
- Schema.org BreadcrumbList — the structured data vocabulary used for SEO
- Google: Breadcrumb Structured Data — Google's guide to breadcrumb rich results
- MDN:
aria-current - MDN:
<nav>element