Breadcrumb

nav.breadcrumb

Breadcrumb — the trail of ancestors leading to the current page.

Examples

A trail

View “A trail” as
<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.

A long trail on one line

View “A long trail on one line” as
<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.

A different separator

View “A different separator” as
<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.

Level 2 tokens declared by breadcrumb
TokenDefault
--breadcrumb-font-familyvar(--font-secondary)
--breadcrumb-item-max-inline-size12rem
--breadcrumb-colorvar(--color-on-surface-muted)
--breadcrumb-color-hovervar(--color-on-surface)
--breadcrumb-current-colorvar(--color-on-surface)
--breadcrumb-current-font-weightvar(--font-weight-medium)
--breadcrumb-font-sizevar(--font-size-xs)
--breadcrumb-gapvar(--space-2)
--breadcrumb-link-gapvar(--space-1)
--breadcrumb-row-gapvar(--space-1)
--breadcrumb-separator"/"
--breadcrumb-separator-colorvar(--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
  • :hover
  • data-legacy

Before you ship it

What you have to do 4 requirements

  1. 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.
  2. 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".
  3. 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.
  4. 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-item and .breadcrumb-link are 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