Skip to main content
Copy for LLM

Breadcrumb

The <x-wirekit::breadcrumb> component renders a navigation trail with customizable separators. It follows the WAI-ARIA Breadcrumb pattern.

Usage

Basic Breadcrumb

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.

Breadcrumb with icons

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

Separator Variants
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:

A long trail on a narrow 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 href in 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 href become 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.

A control beside the trail

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 omits href.
  • 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::personalize():

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/wirekit.php under components.breadcrumb. Override them globally:

'components' => [
    'breadcrumb' => ['separator' => 'slash'], // change default separator
],

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store