Skip to main content
Markdown

Breadcrumb

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. The variant prop switches to the dot, slash, or vertical glyph presets:

<div className="breadcrumb-demo-stack">
<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator variant="dot" />
<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 variant="slash" />
<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 variant="vertical" />
<Breadcrumb.Item>
<Breadcrumb.Page>Account</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>
</div>

Arbitrary content goes through children, which overrides the preset:

<Breadcrumb>
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href="#">Home</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>
<ArrowRightIconOutlinedRounded />
</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Link href="#loyalty">Loyalty</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator>
<ArrowRightIconOutlinedRounded />
</Breadcrumb.Separator>
<Breadcrumb.Item>
<Breadcrumb.Page>Status & miles</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb>

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>
<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 />);

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>

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

See Spar Breadcrumb docs for primitive behavior.

NameTypeDefaultDescription
childrenReact.ReactNode-Breadcrumb.List rendered inside the <nav> landmark.
sizeBreadcrumbSize'base'Density scale cascaded to every part through context.
typeBreadcrumbType'basic'Visual style cascaded to every part through context.
classNamesPartial<Record<"root", string>>-Per-slot class name overrides.
slotPropsPartial<Record<"root", React.HTMLAttributes<HTMLElement>>>-Per-slot HTML attribute overrides.
classNamestring-Appends custom classes to the root slot of this part.
AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-sizeAlwaysReflects the resolved size prop so theme recipes can scope size variants. Emitted by the wrapper.
data-typeAlwaysReflects the resolved type prop (basic | outlined) so theme recipes can scope the visual variant. Emitted by the wrapper.
data-disabledWhen disabled is true.Theme hook for the disabled trail. Emitted by Spar, alongside aria-disabled.

See Spar Breadcrumb docs for primitive behavior.

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.

See Spar Breadcrumb docs for primitive behavior.

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-positionAlways"first", "middle", or "last". Emitted by Spar — with the pinned 0.2.0-beta.1 it always resolves to "middle" through this wrapper (position derivation is type-matched on Spar’s own item); the real values arrive with the Spar context-registration release.
data-currentReserved — not emitted at item level with the pinned Spar.Will mark the current crumb once the Spar context-registration release lands. Until then, style the current crumb via Breadcrumb.Page’s always-emitted data-current.

See Spar Breadcrumb docs for primitive behavior.

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-externalWhen isExternal is true.Theme hook for the external link variant. Emitted by Spar.
data-disabledWhen the link disabled is true, or the root is disabled.Theme hook for the disabled link state. Emitted by Spar.

See Spar Breadcrumb docs for primitive behavior.

AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-currentAlwaysMarks the current page; emitted by Spar on every Breadcrumb.Page, alongside aria-current="page".

See Spar Breadcrumb docs for primitive behavior.

NameTypeDefaultDescription
childrenReact.ReactNode-Override the separator glyph entirely; takes priority over variant. The owner <li aria-hidden> element stays invariant.
variantBreadcrumbSeparatorVariant'chevron'Glyph preset rendered when no children are given.
classNamesPartial<Record<"root", string>>-Per-slot class name overrides.
slotPropsPartial<Record<"root", React.HTMLAttributes<HTMLElement>>>-Per-slot HTML attribute overrides.
classNamestring-Appends custom classes to the root slot of this part.
AttributeApplied whenPurpose
data-slot="root"AlwaysStable selector for wrapper styling on the root slot.
data-variantAlwaysReflects the resolved variant preset (chevron | dot | slash | vertical) so the recipe can paint glyph separators. Emitted by the wrapper.
NameDefinition
BreadcrumbSize'base' | 'large'
BreadcrumbType'basic' | 'outlined'
BreadcrumbSeparatorVariant'chevron' | 'dot' | 'slash' | 'vertical'