# Breadcrumb (/docs/breadcrumb)





`Breadcrumb` renders a trail of links showing the current page's place in a hierarchy — home → … → current page. It's a pure Server Component — no state, no `"use client"`.

```tsx
import { Breadcrumb } from "@42-central/ui-react/breadcrumb";

<Breadcrumb
  withHome={{ href: "/" }}
  data={[
    { label: "Repo", href: "/repo" },
    { label: "File" },
  ]}
/>;
```

## Basic [#basic]

The `data` form is the quickest way to use it — pass an array of crumbs. The last entry defaults to the current, non-interactive crumb unless another entry explicitly sets `current`; separators between crumbs are generated automatically, never placed by hand.

<Preview align="block" code="&#x22;<Breadcrumb\n  withHome={{ href: '/' }}\n  data={[\n    { label: 'ui-react', href: '/ui-react' },\n    { label: 'components', href: '/ui-react/components' },\n    { label: 'breadcrumb.tsx' },\n  ]}\n/>&#x22;">
  <Breadcrumb
    withHome="{ href: &#x22;/&#x22; }"
    data="[
    { label: &#x22;ui-react&#x22;, href: &#x22;/ui-react&#x22; },
    { label: &#x22;components&#x22;, href: &#x22;/ui-react/components&#x22; },
    { label: &#x22;breadcrumb.tsx&#x22; },
  ]"
  />
</Preview>

<Callout type="info" title="The current crumb is muted, not bolded">
  The last crumb renders muted and non-interactive (`aria-current="page"`) — it isn't a link, so it's deliberately de-emphasized rather than made the visually strongest item. That's the inverse of some breadcrumb conventions; it's what this component's source design calls for.
</Callout>

## Compound form [#compound-form]

Drop to `Breadcrumb.Root` + `Breadcrumb.Home`/`Breadcrumb.Item` for full control over composition — same rendering as the `data` form, hand-written instead of generated.

<Preview align="block" code="&#x22;<Breadcrumb.Root>\n  <Breadcrumb.Home href=\&#x22;/\&#x22; />\n  <Breadcrumb.Item href=\&#x22;/repo\&#x22;>Repo</Breadcrumb.Item>\n  <Breadcrumb.Item current>File</Breadcrumb.Item>\n</Breadcrumb.Root>&#x22;">
  <Breadcrumb.Root>
    <Breadcrumb.Home href="/" />

    <Breadcrumb.Item href="/repo">
      Repo
    </Breadcrumb.Item>

    <Breadcrumb.Item>
      File
    </Breadcrumb.Item>
  </Breadcrumb.Root>
</Preview>

## Truncation [#truncation]

The trail always fits its container, and it takes no JavaScript to do it: when space runs out, link crumbs ellipsize first — all the way down to a bare "…" — and the current crumb only starts shrinking once they can't give up any more. The home icon and separators never shrink. Hover or keyboard-focus a crumb to open it back up to its full label. Drag the corner of the box below to try it.

<Preview align="block" code="&#x22;<div className=\&#x22;w-[420px] max-w-full resize-x overflow-hidden p-1\&#x22;>\n  <Breadcrumb\n    withHome={{ href: '/' }}\n    data={[\n      { label: 'packages', href: '/packages' },\n      { label: 'react', href: '/packages/react' },\n      { label: 'components', href: '/packages/react/components' },\n      { label: 'breadcrumb.test.tsx' },\n    ]}\n  />\n</div>&#x22;">
  <div className="w-[420px] max-w-full resize-x overflow-hidden p-1">
    <Breadcrumb
      withHome="{ href: &#x22;/&#x22; }"
      data="[
      { label: &#x22;packages&#x22;, href: &#x22;/packages&#x22; },
      { label: &#x22;react&#x22;, href: &#x22;/packages/react&#x22; },
      { label: &#x22;components&#x22;, href: &#x22;/packages/react/components&#x22; },
      { label: &#x22;breadcrumb.test.tsx&#x22; },
    ]"
    />
  </div>
</Preview>

The full label stays in the DOM, so screen readers always announce it. Inside a flex row (a header next to other controls), the breadcrumb's `<nav>` already carries `min-w-0`, so it shrinks with the row instead of pushing it wider.

## Router links [#router-links]

Pass `linkComponent` to route every generated crumb through your framework's own `Link` instead of a plain `<a>`, so navigation stays client-side. It works on `Breadcrumb`/`Breadcrumb.Root` (the default for every crumb) or per-item (overrides just one):

```tsx
import Link from "next/link";
import { Breadcrumb } from "@42-central/ui-react/breadcrumb";

<Breadcrumb
  linkComponent={Link}
  data={[{ label: "Repo", href: "/repo" }, { label: "File" }]}
/>;
```

Routers whose `Link` takes `to` instead of `href` (React Router) need a two-line local wrapper renaming the prop before passing it as `linkComponent`:

```tsx
import { Link as RouterLink } from "react-router";

const Link = ({ href, ...props }: { href?: string }) => (
  <RouterLink to={href ?? "#"} {...props} />
);
```

For one-off composition, `asChild` (inherited from Ark) still works on any single `Breadcrumb.Item`/`Breadcrumb.Home`, and wins over `linkComponent`:

```tsx
<Breadcrumb.Item asChild>
  <Link href="/repo">Repo</Link>
</Breadcrumb.Item>
```

`Breadcrumb.Home` puts its house icon inside an `asChild` link that has no children of its own, so a bare router link is enough:

```tsx
<Breadcrumb.Home asChild>
  <Link href="/" />
</Breadcrumb.Home>
```

## Back button [#back-button]

Every one of this component's source designs pairs it with a leading "back" icon-button — but that's a router/history action this kit has no opinion on, so it isn't part of `Breadcrumb`. Compose one next to it with `ActionIcon`:

<Preview align="block" code="&#x22;<div className=\&#x22;flex items-center gap-1\&#x22;>\n  <ActionIcon variant=\&#x22;subtle\&#x22; size=\&#x22;sm\&#x22; radius=\&#x22;xs\&#x22; aria-label=\&#x22;Back\&#x22;>\n    <ChevronLeft aria-hidden />\n  </ActionIcon>\n  <Breadcrumb withHome={{ href: '/' }} data={[{ label: 'File' }]} />\n</div>&#x22;">
  <div className="flex items-center gap-1">
    <ActionIcon variant="subtle" size="sm" radius="xs" aria-label="Back">
      <ChevronLeft />
    </ActionIcon>

    <Breadcrumb withHome="{ href: &#x22;/&#x22; }" data="[{ label: &#x22;File&#x22; }]" />
  </div>
</Preview>

## Subcomponents [#subcomponents]

<TypeTable
  type="{
  &#x22;Breadcrumb.Root&#x22;: {
    type: &#x22;component&#x22;,
    description:
      &#x22;The <nav>/<ol> shell. Auto-inserts separators between Home/Item children and threads a default linkComponent to each — same props as Breadcrumb, minus data/withHome. Takes its own `testId`.&#x22;,
  },
  &#x22;Breadcrumb.Home&#x22;: {
    type: &#x22;component&#x22;,
    description:
      &#x22;Icon-only leading crumb, always a link, never current. Defaults to a house icon and aria-label=\&#x22;Home\&#x22;; children overrides the icon. Takes its own `testId`, on whichever of its render branches (asChild / linkComponent / plain <a>) is active.&#x22;,
  },
  &#x22;Breadcrumb.Item&#x22;: {
    type: &#x22;component&#x22;,
    description:
      &#x22;One crumb — a link, or (with current) the non-interactive current page. Takes its own `testId`, on whichever of its render branches (current span / asChild / linkComponent / plain <a>) is active.&#x22;,
  },
}"
/>

Every part renders `testId` as `data-testid` on its own DOM node — see [Testing](/docs/testing).

## API [#api]

### Breadcrumb [#breadcrumb]

<TypeTable
  type="{
  data: {
    type: &#x22;{ label: ReactNode; href?: string; current?: boolean; linkComponent?: ElementType; testId?: string }[]&#x22;,
    description:
      &#x22;The crumbs to render, in order. The last entry defaults to current unless any entry explicitly sets it. Each entry's own `testId` renders as `data-testid` on that crumb, defaulting to `item.href ?? key` (a stable positional fallback) when omitted — see [Testing](/docs/testing).&#x22;,
  },
  withHome: {
    type: &#x22;boolean | (Breadcrumb.HomeProps & { href: string })&#x22;,
    description:
      &#x22;Renders a leading Breadcrumb.Home. true uses the default house icon with no link; pass an object (with href) to link it.&#x22;,
  },
  linkComponent: {
    type: &#x22;ElementType&#x22;,
    description:
      &#x22;A framework's own Link component, substituted for a plain <a> on every generated crumb that doesn't set its own. Must accept href.&#x22;,
  },
  separator: {
    type: &#x22;ReactNode&#x22;,
    default: '&#x22;/&#x22;',
    description: &#x22;Rendered between crumbs, decorative (aria-hidden).&#x22;,
  },
  classNames: {
    type: &#x22;{ list?: string }&#x22;,
    description: &#x22;Styles the inner <ol> (the bordered pill); className targets the outer <nav>.&#x22;,
  },
  &#x22;aria-label&#x22;: {
    type: &#x22;string&#x22;,
    default: '&#x22;Breadcrumb&#x22;',
    description: &#x22;Accessible name for the <nav> landmark.&#x22;,
  },
  testId: {
    type: &#x22;string&#x22;,
    description: &#x22;Renders as `data-testid` on the root `<nav>`. See [Testing](/docs/testing).&#x22;,
  },
}"
/>

`Breadcrumb.Root` takes the same props except `data`/`withHome` — compose `Breadcrumb.Home`/`Breadcrumb.Item` as its children instead.

### Breadcrumb.Item [#breadcrumbitem]

<TypeTable
  type="{
  href: {
    type: &#x22;string&#x22;,
  },
  current: {
    type: &#x22;boolean&#x22;,
    description:
      'Marks this as the current/last crumb: non-interactive, muted, aria-current=&#x22;page&#x22;, no href/anchor.',
  },
  linkComponent: {
    type: &#x22;ElementType&#x22;,
    description: &#x22;Overrides the linkComponent inherited from Breadcrumb.Root.&#x22;,
  },
  asChild: {
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
    description: &#x22;Render the single child as this crumb (Ark UI polymorphism) — wins over linkComponent.&#x22;,
  },
  testId: {
    type: &#x22;string&#x22;,
    description: &#x22;Renders as `data-testid` on whichever element this crumb resolves to — the current-crumb span, the asChild element, or the (custom- or plain-) link. See [Testing](/docs/testing).&#x22;,
  },
}"
/>

### Breadcrumb.Home [#breadcrumbhome]

<TypeTable
  type="{
  href: {
    type: &#x22;string&#x22;,
  },
  linkComponent: {
    type: &#x22;ElementType&#x22;,
    description: &#x22;Overrides the linkComponent inherited from Breadcrumb.Root.&#x22;,
  },
  &#x22;aria-label&#x22;: {
    type: &#x22;string&#x22;,
    default: '&#x22;Home&#x22;',
  },
  asChild: {
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
    description: &#x22;Render the single child as this crumb (Ark UI polymorphism) — wins over linkComponent.&#x22;,
  },
  testId: {
    type: &#x22;string&#x22;,
    description: &#x22;Renders as `data-testid` on whichever element this crumb resolves to — the asChild element, or the (custom- or plain-) link. See [Testing](/docs/testing).&#x22;,
  },
}"
/>

## Accessibility [#accessibility]

* Renders `<nav aria-label="Breadcrumb">` (override via `aria-label`) wrapping an `<ol>`.
* The current/last crumb gets `aria-current="page"` and isn't focusable or a link.
* Separators are decorative — `aria-hidden`, never announced.
* `Breadcrumb.Home` defaults its accessible name to `"Home"` via `aria-label`.
