Breadcrumb

A wayfinding trail composed as a Radix-style compound — Root, List, Item, Link, Page, Separator. The call-site decides which crumbs are links, which is the current page, and what glyph sits between them.

Customize
Shape
3

Installation

npx shadcn@latest add https://craftbits.dev/r/breadcrumb.json

Usage

Breadcrumb is a compound — Root is the <nav> landmark, List is the <ol>, each Item wraps a Link or a Page, and Separator paints the glyph between them.

import { Breadcrumb } from "@craft-bits/core";
 
<Breadcrumb.Root>
  <Breadcrumb.List>
    <Breadcrumb.Item>
      <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
    </Breadcrumb.Item>
    <Breadcrumb.Separator />
    <Breadcrumb.Item>
      <Breadcrumb.Link href="/docs">Docs</Breadcrumb.Link>
    </Breadcrumb.Item>
    <Breadcrumb.Separator />
    <Breadcrumb.Item>
      <Breadcrumb.Page>Breadcrumb</Breadcrumb.Page>
    </Breadcrumb.Item>
  </Breadcrumb.List>
</Breadcrumb.Root>

Swap the default / for any glyph by passing children to Separator:

<Breadcrumb.Separator></Breadcrumb.Separator>

Use asChild on Link to delegate routing to your framework's link primitive — Next's <Link>, React Router's <NavLink>, etc.:

import NextLink from "next/link";
 
<Breadcrumb.Item>
  <Breadcrumb.Link asChild>
    <NextLink href="/docs">Docs</NextLink>
  </Breadcrumb.Link>
</Breadcrumb.Item>

Understanding the component

  1. Compound parts. Root is the <nav aria-label="Breadcrumb"> landmark; List is the <ol> (order matters); Item is each <li>; Link is an <a> (or your framework's link via asChild); Page is the current crumb, non-link; Separator is the glyph between two crumbs. Composing rather than passing a flat items array means one crumb can opt out of being a link without a special prop.
  2. Semantic ordered list. Screen readers announce position ("item 2 of 4") because the trail is an <ol> — sequence is meaningful.
  3. Current page hook. Breadcrumb.Page renders a <span> with aria-current="page" and aria-disabled="true" so assistive tech announces it as "current."
  4. Slot-based polymorphism. Breadcrumb.Link accepts asChild (Radix's Slot pattern). The link's classes merge onto whatever you render as the child.
  5. Configurable separator. The default glyph is a serif-italic /. Pass any node — a chevron, an icon — as children to replace it. The separator is aria-hidden because the <ol> already conveys order to assistive tech.
  6. Focus visible. Each Link gets a focus-visible: ring keyed to --cb-accent, offset from --cb-bg, so keyboard users always see the current target on every theme surface.

Props

Breadcrumb.Root

PropTypeDefaultDescription
classNamestringMerged onto the rendered <nav>.
...restHTMLAttributes<HTMLElement>Any other <nav> prop.

Breadcrumb.List

PropTypeDefaultDescription
classNamestringMerged onto the rendered <ol>.
...restOlHTMLAttributes<HTMLOListElement>Any other <ol> prop.

Breadcrumb.Item

PropTypeDefaultDescription
classNamestringMerged onto the rendered <li>.
...restLiHTMLAttributes<HTMLLIElement>Any other <li> prop.

Breadcrumb.Link

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element instead of an <a> (Radix Slot).
hrefstringDestination — only when not asChild.
classNamestringMerged onto the rendered element.
...restAnchorHTMLAttributes<HTMLAnchorElement>Any other anchor prop.

Breadcrumb.Page

PropTypeDefaultDescription
classNamestringMerged onto the rendered <span>.
...restHTMLAttributes<HTMLSpanElement>Any other <span> prop.

Breadcrumb.Separator

PropTypeDefaultDescription
childrenReactNode'/'Override the default glyph.
classNamestringMerged onto the rendered <span>.
...restHTMLAttributes<HTMLSpanElement>Any other <span> prop.

Accessibility

  • The Root renders <nav aria-label="Breadcrumb">, the canonical W3C ARIA pattern. Pass a different aria-label for a more specific name.
  • The List is an <ol> — assistive tech announces position ("item 2 of 4") because order is meaningful.
  • The current Breadcrumb.Page carries aria-current="page" and aria-disabled="true" so users know they're already on this page.
  • Separators are aria-hidden="true" with role="presentation" — the <ol> already conveys order to assistive tech, so reading "slash" between every crumb would just add noise.
  • Each Link has focus-visible: styling keyed to --cb-accent, offset from --cb-bg, so keyboard users can always see where they are.
  • Color contrast in the default theme: link text uses --cb-fg-muted; the current page uses --cb-fg for stronger emphasis; both pass WCAG AA against --cb-bg.

Credits

  • Extracted from: terminal-dreams (src/components/retro/Breadcrumb.tsx). The original took an items array and animated each crumb with a stagger; craft-bits drops the implicit animation and lifts the API into a Radix-style compound so the trail is composable and a11y-correct out of the box.