Overview
<Breadcrumbs> shows the user's path through a nested hierarchy:
Home / Users / John Smith / SettingsHome / Users / John Smith / SettingsIt ships in two flavors that resolve to the same <nav><ol>…</ol></nav> shape:
- Array API — pass
items={[{ label, href }, …]}. Best for dynamic paths from a router. - Compound API —
<Breadcrumbs.Item>/<Breadcrumbs.Separator>children. Best when items diverge (icons, custom render, mixed link types).
The component supports custom separators, overflow collapse via <Menu>, polymorphic items
for router integration, and full RTL.
Overview — product detail path
Anatomy
| Subpart | Element | Role |
|---|---|---|
<Breadcrumbs> | <nav aria-label="Breadcrumb"> + <ol> | container + ordered list of crumbs |
<Breadcrumbs.Item> | <li> wrapping <span> / <a> / asChild | a single crumb; current paints non-interactive |
<Breadcrumbs.Separator> | <li role="presentation" aria-hidden> | visual divider; SR skips it |
| Overflow trigger (internal) | <li> + <Menu> from apx-ds | collapsed-middle dropdown when maxItems is set |
Examples
The default example is interactive — every link is clickable, and the truncated example below
shows the overflow <Menu> for hidden crumbs.
Basic
Truncated
CustomSeparator
WithIcons
RouterLink
RenderItem
Compound
Variants
Sizes
Colors
Rtl
LongLabels
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Children — compound API form. Ignored when `items` is provided. |
| color | enum | — | — |
| items | BreadcrumbsItem[] | — | Array of crumb items. If omitted, consumers must supply compound `<Breadcrumbs.Item>` children. |
| itemsAfterCollapse | number | 1 | Items at the end to keep when collapsing. |
| itemsBeforeCollapse | number | 1 | Items at the start to keep when collapsing. |
| maxItems | number | — | Maximum number of items to display before collapsing the middle into an overflow menu. |
| overflowAriaLabel | string | — | Accessible label for the overflow menu trigger. Defaults to "Show hidden navigation items". Will be replaced by `<I18nProvider>` translations once the i18n primitive ships. |
| renderItem | ((ctx: BreadcrumbsRenderItemContext) => ReactNode) | — | Custom render fn for every item; overrides default link/span rendering. |
| separator | ReactNode | '/' | Separator node between items. String or React element. |
| separatorColor | enum | 'muted' | Separator paint role. |
| size | ResponsiveValue<BreadcrumbsSize> | — | — |
| sx | Sx | — | Theme-aware inline style object. |
| variant | ResponsiveValue<BreadcrumbsVariant> | — | — |
Truncation / overflow
When items.length > maxItems, the middle items collapse into a single <Menu> trigger
(visualized as an ellipsis button). itemsBeforeCollapse / itemsAfterCollapse (default 1
each) control how many items remain on each end.
The overflow dropdown is rendered through the same <Menu> primitive that powers the rest of
the DS — no custom dropdown logic lives in Breadcrumbs. Clicking a hidden crumb
navigates to its href / to; consumers using a router should provide renderItem so each
hidden crumb renders inside the menu as their router-aware element.
Accessibility
- Root is
<nav aria-label="Breadcrumb">(override viaaria-labelprop). The<ol>inside preserves order semantics — screen readers announce "1 of N", "2 of N", etc. - Each crumb is an
<li>wrapping its content (span for current / asChild for link). - Last item (or any item with
current={true}/ nohref) carriesaria-current="page". - Separators have
role="presentation"+aria-hidden="true"so SRs don't read "slash slash slash" between every item. - Overflow trigger is a
<button aria-label="Show hidden navigation items" aria-haspopup="menu">. - Keyboard: native Tab through link items; the overflow Menu uses Menu's existing keyboard
pattern (
Enter/ arrows / type-ahead). - axe-core: zero violations across the variant × color matrix + overflow + compound API cells.
Router integration
import Link from 'next/link';
<Breadcrumbs>
<Breadcrumbs.Item asChild>
<Link href="/">Home</Link>
</Breadcrumbs.Item>
<Breadcrumbs.Item asChild>
<Link href="/users">Users</Link>
</Breadcrumbs.Item>
<Breadcrumbs.Item current>John Smith</Breadcrumbs.Item>
</Breadcrumbs>;import Link from 'next/link';
<Breadcrumbs>
<Breadcrumbs.Item asChild>
<Link href="/">Home</Link>
</Breadcrumbs.Item>
<Breadcrumbs.Item asChild>
<Link href="/users">Users</Link>
</Breadcrumbs.Item>
<Breadcrumbs.Item current>John Smith</Breadcrumbs.Item>
</Breadcrumbs>;For dynamic paths, the array API plus renderItem is usually cleaner:
<Breadcrumbs
items={crumbsFromRouter}
renderItem={({ item, isCurrent, defaultClassName }) =>
isCurrent ? (
<span aria-current="page" className={defaultClassName}>
{item.label}
</span>
) : (
<Link href={item.href ?? item.to ?? '#'} className={defaultClassName}>
{item.label}
</Link>
)
}
/><Breadcrumbs
items={crumbsFromRouter}
renderItem={({ item, isCurrent, defaultClassName }) =>
isCurrent ? (
<span aria-current="page" className={defaultClassName}>
{item.label}
</span>
) : (
<Link href={item.href ?? item.to ?? '#'} className={defaultClassName}>
{item.label}
</Link>
)
}
/>Theming
Four override slots are exposed: root, list, item, separator, overflowTrigger. Each
is merged through the same useThemedClasses precedence chain every DS component uses.
<ThemeProvider
theme={defineTheme({
components: {
Breadcrumbs: {
defaultProps: { variant: 'soft', color: 'primary' },
styleOverrides: {
list: 'rounded-md bg-bg-subtle px-2 py-1',
item: 'font-medium',
separator: 'opacity-50',
},
},
},
})}
>
{children}
</ThemeProvider><ThemeProvider
theme={defineTheme({
components: {
Breadcrumbs: {
defaultProps: { variant: 'soft', color: 'primary' },
styleOverrides: {
list: 'rounded-md bg-bg-subtle px-2 py-1',
item: 'font-medium',
separator: 'opacity-50',
},
},
},
})}
>
{children}
</ThemeProvider>RTL
- The
<ol>flows right-to-left automatically insidedir="rtl". - Long-label truncation uses logical-CSS
truncate+max-w-*— works in both directions. - Chevron separators should use a logical icon (
<ChevronEnd />) so they flip; symmetric separators like/or·need no change.
Do / Don't
- Do use the array API for dynamic paths from your router.
- Do put
aria-current="page"on the destination crumb (the array API does this for you when the last item has nohref). - Do use
maxItemsto keep deep paths inside narrow containers. - Don't wrap individual crumbs in a separate
<nav>— the root already handles that. - Don't put critical actions in a breadcrumb — it's a navigation indicator, not a toolbar.