Breadcrumb
Navigation trail showing the user's location in a hierarchy, with collapsible middle levels, custom separators, icons and RTL.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
BreadcrumbSkeleton,
} from "@tessinaui/ui";<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>The trail is composed, not configured: you write the crumbs, so you decide how many there are, which one is current, and where a level is left out. The component owns the landmark, the list semantics, the separators and the scale.
Where the trail ends
Both shapes are correct and both are common:
- End on the current page — the last crumb is a
BreadcrumbPage. Use this when the page title is not visible on screen, or repeats far down the page. - End on the parent — every crumb is a link and the page's own
<h1>is the current node. Most storefronts do this (the trail sits directly above the product title), and it saves reading the same words twice.
When not to use one
- A linear flow — checkout, onboarding, a wizard. Those are steps, not a
hierarchy; use
Stepper, or a progress indicator. - A flat site — with one level below home, a breadcrumb says nothing a back affordance does not.
- As the only navigation — a breadcrumb is secondary wayfinding. It tells you where you are, not where else you can go; pair it with a header, sidebar or tabs.
- Switching between siblings at the same level — that is a menu on a crumb
(below), or
Tabs.
Examples
Default
A trail of links ending in the current page.
Sizes
Five scales from xs to xl. Text, separator and ellipsis all step together;
on phones every crumb keeps a 44px touch target regardless of size.
Separators
separator="chevron" (default), "slash" or "dot" — or pass your own node to
BreadcrumbSeparator. Separators are hidden from assistive tech: they are
punctuation, and a screen reader that reads "slash" between every crumb is
noise.
Collapsed
BreadcrumbEllipsis marks levels that were left out, so a deep trail still fits
one line.
Collapsed, with the hidden levels reachable
Better, when the levels matter: put them behind a real button. The name counts what is hidden ("3 more levels") rather than announcing an ellipsis.
A crumb that opens its siblings
render lets a crumb be whatever element it needs to be — a menu trigger here,
a framework router's <Link> in an app — while keeping the trail's own ink,
size and focus ring.
Icons
An icon beside a label is decorative. An icon-only crumb needs its own
aria-label, or it announces as an empty link.
Counts and status
A badge inside a crumb joins that crumb's accessible name, so keep it short.
Long labels
The trail wraps by default, which is right on a phone. To hold it to one line, cap the crumb and let it ellipsize — the accessible name is untouched, so a screen reader still reads the whole label.
RTL
Set dir="rtl" and the trail mirrors, the built-in chevron turns, and anything
opened from a crumb lands on the correct side. Leave it unset and the component
follows the page.
Background
background paints the trail on a filled plate, which also re-publishes the
surface contract so a focus ring inside draws its gap in the plate's colour.
Loading
BreadcrumbSkeleton is the same box as the real trail. Silent by default; pass
label when this trail is the region a reader should be told about.
Accessibility
- The root is a
<nav>landmark named Breadcrumb, wrapping an ordered list — the shape the ARIA Authoring Practices Guide prescribes. Passaria-labelto translate that name; do not remove it. - The current page carries
aria-current="page". It is a plain span, not a disabled link: the page you are already on is not a broken destination. - Separators are
aria-hidden. They exist visually and nowhere else. BreadcrumbEllipsisannounces "More pages" (override withlabel), while its glyph is hidden. When you wrap it in a button, name the button and hide the ellipsis inside it — otherwise the same thing is announced twice.- No keyboard interaction to learn. A breadcrumb is a row of links, so Tab and Enter are the whole model. Anything more means you have built a menu, and it should say so with the right role.
- Touch targets are ≥44px on phones. Crumbs wrap rather than scroll, so no level is ever placed out of reach.
API Reference
Breadcrumb
Renders <nav aria-label="Breadcrumb">.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Scales text, separator and ellipsis together |
rounded | "none" | "sm" | "md" | "lg" | "full" | "full" | Corner scale. Rounds the background plate, and steps each crumb's focus corner with it |
separator | "chevron" | "slash" | "dot" | "chevron" | Glyph rendered by BreadcrumbSeparator when it has no children |
dir | "ltr" | "rtl" | — | Writing direction. Unset, the trail inherits the page's; when set it also directs any popup opened from a crumb |
background | boolean | false | Paints the trail on a filled plate with size-appropriate padding |
Forwards className, ref and any other <nav> attribute.
BreadcrumbList
Renders <ol>. Takes the row's gap and type scale from size.
BreadcrumbItem
Renders <li>, carrying the phone touch floor.
BreadcrumbLink
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Destination |
disabled | boolean | false | Mutes the crumb, drops pointer events and removes it from the tab order |
render | React.ReactElement | — | Render as this element instead — a router <Link>, a menu trigger. Classes and props merge onto it, and both refs are kept |
Forwards every other <a> attribute.
BreadcrumbPage
The current page: a <span aria-current="page">. Forwards all span attributes.
BreadcrumbSeparator
Renders <li role="presentation" aria-hidden="true">. Empty, it draws the
trail's separator; with children, exactly what you pass:
<BreadcrumbSeparator>
<span aria-hidden="true">→</span>
</BreadcrumbSeparator>The built-in chevron mirrors itself under RTL. A separator you supply is left alone — mirroring an arbitrary glyph is as likely to be wrong as right.
BreadcrumbEllipsis
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "More pages" | What assistive tech hears in place of the glyph |
BreadcrumbSkeleton
| Prop | Type | Default | Description |
|---|---|---|---|
items | number | 3 | Crumb placeholders, including the current page |
labels | number[] | — | Per-crumb label lengths in characters, so each bar measures what its label will |
collapsed | boolean | false | Render first · … · last two, the shape a collapsed trail produces |
label | string | — | Announcement for assistive tech. Silent when unset |
size · rounded · separator · background · dir | — | mirrors Breadcrumb | Keep these equal to the real trail's so nothing shifts when it arrives |