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.jsonUsage
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
- 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 viaasChild); Page is the current crumb, non-link; Separator is the glyph between two crumbs. Composing rather than passing a flatitemsarray means one crumb can opt out of being a link without a special prop. - Semantic ordered list. Screen readers announce position ("item 2 of 4") because the trail is an
<ol>— sequence is meaningful. - Current page hook.
Breadcrumb.Pagerenders a<span>witharia-current="page"andaria-disabled="true"so assistive tech announces it as "current." - Slot-based polymorphism.
Breadcrumb.LinkacceptsasChild(Radix'sSlotpattern). The link's classes merge onto whatever you render as the child. - Configurable separator. The default glyph is a serif-italic
/. Pass any node — a chevron, an icon — as children to replace it. The separator isaria-hiddenbecause the<ol>already conveys order to assistive tech. - 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
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Merged onto the rendered <nav>. |
...rest | HTMLAttributes<HTMLElement> | — | Any other <nav> prop. |
Breadcrumb.List
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Merged onto the rendered <ol>. |
...rest | OlHTMLAttributes<HTMLOListElement> | — | Any other <ol> prop. |
Breadcrumb.Item
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Merged onto the rendered <li>. |
...rest | LiHTMLAttributes<HTMLLIElement> | — | Any other <li> prop. |
Breadcrumb.Link
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render as the child element instead of an <a> (Radix Slot). |
href | string | — | Destination — only when not asChild. |
className | string | — | Merged onto the rendered element. |
...rest | AnchorHTMLAttributes<HTMLAnchorElement> | — | Any other anchor prop. |
Breadcrumb.Page
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Merged onto the rendered <span>. |
...rest | HTMLAttributes<HTMLSpanElement> | — | Any other <span> prop. |
Breadcrumb.Separator
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | '/' | Override the default glyph. |
className | string | — | Merged onto the rendered <span>. |
...rest | HTMLAttributes<HTMLSpanElement> | — | Any other <span> prop. |
Accessibility
- The Root renders
<nav aria-label="Breadcrumb">, the canonical W3C ARIA pattern. Pass a differentaria-labelfor a more specific name. - The List is an
<ol>— assistive tech announces position ("item 2 of 4") because order is meaningful. - The current
Breadcrumb.Pagecarriesaria-current="page"andaria-disabled="true"so users know they're already on this page. - Separators are
aria-hidden="true"withrole="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-fgfor stronger emphasis; both pass WCAG AA against--cb-bg.
Credits
- Extracted from:
terminal-dreams(src/components/retro/Breadcrumb.tsx). The original took anitemsarray 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.