Breadcrumb
Breadcrumb — the trail of ancestors leading to the current page.
Examples
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="#home">Home</a>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="#docs">Documentation</a>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="#breadcrumb" aria-current="page">Breadcrumb</a>
</li>
</ol>
</nav>The separator is a pseudo-element, so a consumer cannot forget to hide it from a screen reader and it never lands in a copied selection. Change it with --breadcrumb-separator.
<nav class="breadcrumb breadcrumb-truncate" aria-label="Breadcrumb">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#a">Home</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#b">Knowledge base</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#c">Account and billing</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#d">Invoices and receipts</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#e" aria-current="page">Downloading an invoice</a></li>
</ol>
</nav>Wrapping is the default because it hides nothing: a trail that runs out of room becomes two lines and every label stays readable.
.breadcrumb-truncate is the other answer — one line, each label capped at --breadcrumb-item-max-inline-size and ellipsised past it — and .breadcrumb-scroll keeps one line that scrolls sideways, which is the right choice on a phone where the last crumb is the one you want in view. None of the three ever pushes the page into a horizontal scroll: .breadcrumb-item sets min-inline-size: 0 so a flex item can shrink below its content, which is the declaration whose absence is why so many breadcrumbs do.
<nav class="breadcrumb" aria-label="Breadcrumb, chevron" style="--breadcrumb-separator: '›'">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#a">Home</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#b">Settings</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#c" aria-current="page">Billing</a></li>
</ol>
</nav>
<nav class="breadcrumb" aria-label="Breadcrumb, bullet" style="--breadcrumb-separator: '•'">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#a">Home</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#b">Settings</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#c" aria-current="page">Billing</a></li>
</ol>
</nav>
<nav class="breadcrumb" aria-label="Breadcrumb, guillemet"
style="--breadcrumb-separator: '»'; --breadcrumb-gap: 0.5rem">
<ol class="breadcrumb-list" role="list">
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#a">Home</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#b">Settings</a></li>
<li class="breadcrumb-item"><a class="breadcrumb-link" href="#c" aria-current="page">Billing</a></li>
</ol>
</nav>One declaration: --breadcrumb-separator is a string, so any glyph the typeface has will do — and it is a token, so a skin can set it once rather than every page choosing its own.
It is deliberately not an image or an <svg> slot: the separator is drawn by a pseudo-element with the alternative-text syntax content: "›" / "", which is what keeps it out of the accessibility tree and out of a copied selection at the same time. A glyph in the markup would have to be hidden by hand on every crumb, and forgetting once gives a screen reader "Home slash Settings slash Billing". The three aria-labels here differ only because three <nav>s on one page must be told apart; a real page has one. Pick a glyph the eye reads as "and then" — a chevron, a slash, a guillemet. An arrow is not one of them: it reads as a transition or a mapping, which is a different sentence about the same three links.
Tokens 12
Level 2, declared on .breadcrumb itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --breadcrumb-font-family | var(--font-secondary) |
| --breadcrumb-item-max-inline-size | 12rem |
| --breadcrumb-color | var(--color-on-surface-muted) |
| --breadcrumb-color-hover | var(--color-on-surface) |
| --breadcrumb-current-color | var(--color-on-surface) |
| --breadcrumb-current-font-weight | var(--font-weight-medium) |
| --breadcrumb-font-size | var(--font-size-xs) |
| --breadcrumb-gap | var(--space-2) |
| --breadcrumb-link-gap | var(--space-1) |
| --breadcrumb-row-gap | var(--space-1) |
| --breadcrumb-separator | "/" |
| --breadcrumb-separator-color | var(--color-on-surface-subtle) |
Variants and states
Variants
.breadcrumb-lg
Inside it
.breadcrumb-item.breadcrumb-truncate.breadcrumb-link.breadcrumb-list.breadcrumb-scroll
State it reads
Read from the platform, never mirrored into a class that could disagree with it.
:focus:hoverdata-legacy
Before you ship it
What you have to do 4 requirements
- Name the landmark:
aria-label="Breadcrumb"on the<nav>. Without it the trail is an unlabelled navigation region, and a page usually has more than one. CSS cannot supply this. - Keep
role="list"on the<ol>. The reset strips markers from any classed list, and stripping markers is what makes VoiceOver stop announcing "list, 3 items". - Put
aria-current="page"on the last crumb, and only there. It may sit on an<a>(a self-link, which keeps the trail uniform for keyboard users) or on a<span>; this file styles either. - Put the class on the anchor itself.
.breadcrumb-list a { … }leaves the anchors classless and the reset hands them back the UA underline..breadcrumb-list,.breadcrumb-itemand.breadcrumb-linkare only meaningful inside.breadcrumb; each property that reads a component token carries a fallback, so one used loose degrades to the page defaults instead of to an undefined variable.
src/css/components/breadcrumb.css · npx mostlycss add breadcrumb