|Kit

Breadcrumb

Polymorphic

A trail of links showing the current page's place in a hierarchy, with an auto-muted current crumb and router-link support.

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".

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

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

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.

Code

<Breadcrumb
  withHome={{ href: '/' }}
  data={[
    { label: 'ui-react', href: '/ui-react' },
    { label: 'components', href: '/ui-react/components' },
    { label: 'breadcrumb.tsx' },
  ]}
/>
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.

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.

Code

<Breadcrumb.Root>
  <Breadcrumb.Home href="/" />
  <Breadcrumb.Item href="/repo">Repo</Breadcrumb.Item>
  <Breadcrumb.Item current>File</Breadcrumb.Item>
</Breadcrumb.Root>

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.

Code

<div className="w-[420px] max-w-full resize-x overflow-hidden p-1">
  <Breadcrumb
    withHome={{ href: '/' }}
    data={[
      { label: 'packages', href: '/packages' },
      { label: 'react', href: '/packages/react' },
      { label: 'components', href: '/packages/react/components' },
      { label: 'breadcrumb.test.tsx' },
    ]}
  />
</div>

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.

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):

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:

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:

<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:

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

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:

Code

<div className="flex items-center gap-1">
  <ActionIcon variant="subtle" size="sm" radius="xs" aria-label="Back">
    <ChevronLeft aria-hidden />
  </ActionIcon>
  <Breadcrumb withHome={{ href: '/' }} data={[{ label: 'File' }]} />
</div>

Subcomponents

Prop

Type

Every part renders testId as data-testid on its own DOM node — see Testing.

API

Prop

Type

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

Prop

Type

Prop

Type

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.

Changelog

v0.18.0
  • asChild is a compile error on the data forms of Accordion, Tabs and Breadcrumb, on the ready-made Notification and on AppShell.Sidebar (their Root parts keep it)

    #294
  • Breadcrumb.Home asChild puts its house icon inside an empty link

    #288
v0.17.0
  • Crumbs truncate to fit the container, with CSS alone

    #264
v0.5.0
  • New Breadcrumb component

    #62

On this page