Components

Breadcrumb

One line above a subpage title saying where the page sits. A list of links, the page you are on as a <span>, and a separator that is drawn rather than typed — a 1 px hairline on the steep isometric angle. No script, no glyph, no character to hide from a screen reader.

Default

Three levels, which is what a blog article on this site actually has. The last crumb is the page you are on: not a link, and set in full ink so the one thing that has to be distinguishable — which crumb is not a target — is distinguishable without hovering anything.

Two levels and four

Two is the floor at which the component says anything at all, and four is the ceiling. Past four the trail is telling you the information architecture is too deep, and the answer is the architecture rather than an ellipsis.

The fold

Below 30 rem of container the trail keeps the parent and the current page and removes everything above them. It is a container query, not a media query, so the component folds on its own width — the same contract Pagination folds under. The box below is 22 rem wide at any viewport, so this is the folded state whatever you are reading it on.

The current crumb is also the only item allowed to shrink, so a long title truncates with an ellipsis rather than pushing the trail sideways. An ancestor never truncates: a link whose destination you can no longer read is worse than a link that is not there.

Why not an overflow menu. Carbon collapses the middle of a long trail into a menu and Fluent does something close to it. Both need a script and both put a second interactive thing inside a component whose entire job is one line of orientation — and the crumb a reader on a phone actually uses is the parent, because it is the way back up. Everything above the parent is one tap further away, not gone. USWDS reaches the same place from the other direction: “mobile-friendly designs might show only direct parents.”

The separator

Every other system reaches for a character — /, , » — set in a ::before. Three things are wrong with that here.

content: "" on a 1 px box rotated to a brand angle has none of those problems. There is no text, so there is nothing to announce and nothing to suppress. It is a hairline, which is what this system draws everything with. And it lands on --angle-a, 63.43° — the steep isometric, the same line the lattice runs. The rotation is written as --angle-b because the box starts vertical and the two angles are complements: 90 − 26.57 = 63.43. Move --angle-b and every separator on the site follows it.

It passes the line test. Where a line may go bans a line that is neither an edge, a division nor a label rule, and a separator looks exactly like the decoration that rule is aimed at. It is not one: there is a crumb on each side of it, which is the definition of a division. The banned shape is the bar with nothing on its far side.

Markup

<nav class="cf-breadcrumb" aria-label="Sie sind hier">
  <ol class="cf-breadcrumb__list" role="list">
    <li class="cf-breadcrumb__item"><a class="cf-breadcrumb__link" href="/">Home</a></li>
    <li class="cf-breadcrumb__item"><a class="cf-breadcrumb__link" href="/news">News</a></li>

    <!-- the current page is a span, never a link -->
    <li class="cf-breadcrumb__item">
      <span class="cf-breadcrumb__current" aria-current="page">Digitale Zwillinge im Energiesektor</span>
    </li>
  </ol>
</nav>

It goes inside .cf-page-header__body, immediately above the title, and it brings its own bottom margin — the component owns that distance the way .cf-pagination owns the air above itself, because it belongs to the shape and every page wants the same one. Nothing else on the page sets it.

Anatomy

PartValue
RowGeist Mono, 11 px, uppercase, --tracking-label, one line, never wraps
Crumb at rest--text-secondary, no underline, no contour
Crumb on hover or focus--text-primary plus an underline at 0.25em offset, 120 ms
Current crumb--text-primary, a <span>, truncates with an ellipsis
Separatora 12 px border-inline-start in currentColor, rotated to --angle-a (63.43°)
Separator margins--space-3 (12 px) each side — about 9 px of visible air after the lean
Target28 px tall (--space-2 above and below the 11 px line), ~29 px apart
Air below the trail--space-4 (16 px), the component's own margin
Fold30 rem of container — parent and current page kept, everything above removed
Maximum depthfour crumbs, counting the current page
Why there is no lime, no plate and no contour. A breadcrumb is the quietest thing on a subpage — it is read once, by someone who arrived from a search result and wants to know where they landed. The current crumb takes an ink step and nothing else. Pagination spends a black plate on “this is the one” because there the current page is one of eleven identical slots; here it is the last word on a line that reads left to right, and position already says it.

Where it goes

Structured data is the server's, not the template's. USWDS ships an RDFa variant, and a schema.org BreadcrumbList in JSON-LD does the same job with no markup in the trail at all — one itemListElement per crumb, in order. It is worth having, and it is deliberately not hard-coded into the pattern page: a machine-readable trail that drifts from the visible one is worse than none, so it has to be generated from the same data the crumbs are. Whatever renders the trail renders the JSON-LD.

Accessibility

Rules

Do

  • Put it above the page title, inside the page header, on pages two or more levels deep.
  • Name the crumbs the way the destination names itself — the nav's word, the listing's word.
  • Shorten the current crumb if the title is long. It restates the H1; it does not have to repeat it.
  • Keep it to four crumbs. If you need five, the problem is the hierarchy.
  • Generate the JSON-LD from the same data as the trail, or ship neither.

Don't

  • Don't link the current page, and don't leave it out — it is what carries aria-current.
  • Don't put a filter, a search or a tag in the trail. Hierarchy only.
  • Don't use a character as the separator, and don't put an icon between crumbs.
  • Don't let it wrap to a second line — two rows of labels stop reading as one path.
  • Don't add it to a top-level page, to 404, or to a linear process.