Breadcrumb shows the user's current location as a trail of links back through
the site hierarchy, so they can jump up the path without using the browser back
button.
Usage
import { Breadcrumb } from '@takeoff-ui/react-spar';
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link />
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page />
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
Playground
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#flights">Flights</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Istanbul → London</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
Size
<div className="breadcrumb-demo-stack">
<Breadcrumb size="base">
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb size="large">
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
Variant
type="outlined" wraps each crumb in a bordered, backgrounded chip;
type="basic" (the default) is the bare text trail. A label-less (icon-only)
chip opts into tighter padding with the tk-breadcrumb-item-icon-only class —
<Breadcrumb.Item className="tk-breadcrumb-item-icon-only"> — since the
compound API has no label prop to auto-detect it.
<div className="breadcrumb-demo-stack">
<Breadcrumb type="basic">
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#flights">Flights</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Istanbul → London</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb type="outlined">
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#flights">Flights</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Istanbul → London</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
Separators
Breadcrumb.Separator defaults to a chevron. Pass children to replace it with
text or an icon while keeping the same <li aria-hidden> owner node:
<div className="breadcrumb-demo-stack">
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>•</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>/</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>|</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>
<ArrowRightIconOutlinedRounded />
</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
Icons
Compose an icon before the label inside Breadcrumb.Link or Breadcrumb.Page —
the recipe sizes direct svg/img children and tones them with the crumb's
text color:
<div className="breadcrumb-demo-stack">
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">
<LocationOnIconOutlinedRounded />
Home
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#flights">Flights</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Istanbul → London</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#notifications">
<NotificationIconOutlinedRounded />
Notifications
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#notifications-inbox">
<PersonIconOutlinedRounded />
Team inbox
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>
<PublicIconOutlinedRounded />
All updates
</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
External Link
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="https://help.example.com" isExternal>
Help center
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Refunds</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
Routing Integration
onNavigate runs for every Breadcrumb.Link activation (click,
Enter, or Space). Spar prevents the native navigation
before invoking it, so hand the destination straight to your client-side router
— no event.preventDefault() needed. A link-level onPress takes priority and
short-circuits onNavigate when both are set.
function NavigateBreadcrumbDemo() {
const [last, setLast] = useState(null);
const handleNavigate = (href) => {
setLast(href);
};
return (
<div className="breadcrumb-demo-stack">
<div className="breadcrumb-demo-note">
{last ? 'Navigate to ' + last : 'Click a link to intercept routing'}
</div>
<Breadcrumb onNavigate={handleNavigate}>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#booking">Booking</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Passenger details</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
);
}
render(<NavigateBreadcrumbDemo />);
Custom link components
Every part is polymorphic via as, so a router's link component can replace the
anchor directly — no onNavigate interception needed:
<Breadcrumb.Link as={Link} to="/flights">
Flights
</Breadcrumb.Link>
Breadcrumb.Item also accepts a render function receiving
{ position, isCurrent, isDisabled } for state-driven content:
<Breadcrumb.Item>
{({ isDisabled }) => <Crumb muted={isDisabled} />}
</Breadcrumb.Item>
Disabled
Setting disabled on the root cascades to every Breadcrumb.Link: it removes
the anchor's href, sets aria-disabled on both the <nav> and each link,
drops the disabled links from the tab order (tabindex="-1"), and surfaces
data-disabled on the trail so theme recipes can tone the colors down.
<Breadcrumb disabled>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#checkout">Checkout</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Payment</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
Long Trails
Crumbs never wrap: labels stay on a single line and the trail scrolls
horizontally (with a slim themed scrollbar) when it outgrows its container. The
scroll container reserves a small gutter so the focus ring stays fully visible
while tabbing through an overflowing trail.
<div style={{ maxWidth: 360 }}>
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#booking">Booking</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#booking-flights">Flights</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Link href="#booking-flights-seats">
Seat selection
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>Passenger details and baggage</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>
Collapsed Trail
Deep trails can keep only the first and last crumb visible and fold the middle
ones behind an ellipsis. The ellipsis crumb is a customizable area left to the
consumer:
function CollapsedTrailDemo() {
const hiddenCrumbs = [
{ href: '#booking', label: 'Booking' },
{ href: '#booking-flights', label: 'Flights' },
{ href: '#booking-flights-seats', label: 'Seat selection' },
];
const [expanded, setExpanded] = useState(false);
return (
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
{expanded ? (
hiddenCrumbs.map((crumb) => (
<React.Fragment key={crumb.href}>
<Breadcrumb.Item>
<Breadcrumb.Link href={crumb.href}>{crumb.label}</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
</React.Fragment>
))
) : (
<React.Fragment>
<Breadcrumb.Item>
<Breadcrumb.Link
as="button"
type="button"
style={{ appearance: 'none', background: 'none', border: 0, margin: 0, padding: '0 2px' }}
aria-expanded={false}
aria-label={'Show ' + hiddenCrumbs.length + ' hidden crumbs'}
onPress={() => setExpanded(true)}
>
…
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
</React.Fragment>
)}
<Breadcrumb.Item>
<Breadcrumb.Page>Passenger details</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
);
}
render(<CollapsedTrailDemo />);
Accessibility & Keyboard
Breadcrumb renders a <nav> landmark labelled "Breadcrumb" by default;
pass aria-label to localize it or scope it to the surrounding page.
Breadcrumb.List is an <ol>, so assistive tech announces the trail as an
ordered list.
Breadcrumb.Page marks the current location with aria-current="page".
Breadcrumb.Separator renders an <li aria-hidden> so screen readers do not
announce the chevron between items.
- Links are native
<a> elements, so focus and tab order follow standard anchor
semantics. When a routing handler (onNavigate or onPress) is attached,
both
Enter and Space activate it via
event.preventDefault(); without one, only native Enter activation
applies.
API Reference
Breadcrumb
See
Spar Breadcrumb docs
for primitive behavior.
Props
| Name | Type | Default | Description |
|---|
| children | React.ReactNode | - | Breadcrumb.List rendered inside the <nav> landmark. |
| size | BreadcrumbSize | 'base' | Density scale. Emitted as data-size on the root <nav>; the recipe scopes every part from there. Shared with every part through context. |
| type | BreadcrumbType | 'basic' | Visual style. Emitted as data-type on the root <nav>; the recipe scopes every part from there. Shared with every part through context. |
| classNames | Partial<Record<"root", string>> | - | Per-slot class name overrides. |
| slotProps | Partial<Record<"root", React.HTMLAttributes<HTMLElement>>> | - | Per-slot HTML attribute overrides. |
| className | string | - | Appends custom classes to the root slot of this part. |
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-size | Always | Reflects the resolved size prop so theme recipes can scope size variants. Emitted by the wrapper. |
| data-type | Always | Reflects the resolved type prop (basic | outlined) so theme recipes can scope the visual variant. Emitted by the wrapper. |
| data-disabled | When disabled is true. | Theme hook for the disabled trail. Emitted by Spar, alongside aria-disabled. |
Breadcrumb.List
See
Spar Breadcrumb docs
for primitive behavior.
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
Breadcrumb.Item
See
Spar Breadcrumb docs
for primitive behavior.
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-position | Always | "first", "middle", or "last" in list order. Emitted by Spar: items register with Breadcrumb.List through context, so positions recompute as crumbs mount and unmount. |
| data-current | On the last item. | Marks the current crumb; emitted by Spar on the last registered item, alongside Breadcrumb.Page’s own data-current. |
Breadcrumb.Link
See
Spar Breadcrumb docs
for primitive behavior.
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-external | When isExternal is true. | Theme hook for the external link variant. Emitted by Spar. |
| data-disabled | When the link disabled is true, or the root is disabled. | Theme hook for the disabled link state. Emitted by Spar. |
Breadcrumb.Page
See
Spar Breadcrumb docs
for primitive behavior.
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
| data-current | Always | Marks the current page; emitted by Spar on every Breadcrumb.Page, alongside aria-current="page". |
Breadcrumb.Separator
See
Spar Breadcrumb docs
for primitive behavior.
Data attributes
| Attribute | Applied when | Purpose |
|---|
| data-slot="root" | Always | Stable selector for wrapper styling on the root slot. |
Type Definitions
| Name | Definition |
|---|
| BreadcrumbSize | 'base' | 'large' |
| BreadcrumbType | 'basic' | 'outlined' |