Breadcrumb
The trail to the current page. Routing is yours — the link part is a styled anchor, not a router link.
Added 14 October 2026
Routing is the consumer’s
BreadcrumbLink / hlmBreadcrumbLink is a styled <a> and nothing more. Put your own routing on it:
<BreadcrumbLink href="/campaigns">Campaigns</BreadcrumbLink>
<a hlmBreadcrumbLink routerLink="/campaigns">Campaigns</a>
Upstream spartan helm composes Angular’s RouterLink into the link, which would make @angular/router a
dependency of this package for every Angular consumer — the ./angular bundle imports its peers at top level,
so even an app that never renders a breadcrumb would need it. React has no such coupling, so both sides stay
router-agnostic and the two APIs keep the same shape.
Structure
<nav hlmBreadcrumb aria-label="Campaign breadcrumb">
<ol hlmBreadcrumbList>
<li hlmBreadcrumbItem><a hlmBreadcrumbLink href="/campaigns">Campaigns</a></li>
<li hlmBreadcrumbSeparator></li>
<li hlmBreadcrumbItem aria-hidden="true"><hlm-breadcrumb-ellipsis /></li>
<li hlmBreadcrumbSeparator></li>
<li hlmBreadcrumbItem><span hlmBreadcrumbPage>Line items</span></li>
</ol>
</nav>
- The last crumb is
BreadcrumbPage— a plain<span aria-current="page">. Not a link, because it goes nowhere. BreadcrumbSeparatorrenders a chevron by default; pass children to override (/,›). It isaria-hidden, which also keeps the<ol>’s item count honest.BreadcrumbEllipsiscollapses a middle section. Putaria-hiddenon its wrapping item too — otherwise the list reports a counted, empty entry. [a11y]- Override
aria-labelwhen a page has more than one breadcrumb. Identical landmark names are indistinguishable in a landmark list.
Deviations from upstream, both accessibility fixes
- The current crumb is not
role="link". Upstream addsrole="link" aria-disabled="true"; we keep onlyaria-current="page".aria-disabledis inert on an element nothing can focus, and the role puts an unusable entry in a screen reader’s links list. The crumb is in the trail because of thenav→ol→listructure — the role never contributed to that. - Links are underlined and the current crumb is
font-medium. Upstream distinguishes them by colour alone (text-muted-foregroundvstext-foreground), which in the dark theme are 1.15:1 apart — effectively identical. Colour must not be the only carrier (WCAG 1.4.1), so there is a weight difference and an underline; the underline doubles as the link affordance.
Known token debt: the links sit on --muted-foreground, which is 4.26:1 on the page background — just under
the 4.5:1 that 14px text needs. Fixing it means darkening the token, which affects every muted text in the
library, so it is tracked in scripts/check-contrast.mjs rather than patched here.
Rules
- Breadcrumbs reflect hierarchy, not history. If it changes depending on where the user came from, it is not a breadcrumb.
- Three to five crumbs. Collapse the middle with the ellipsis rather than wrapping to two lines.
- Don’t repeat the page’s
h1as the last crumb and the heading — one of them is enough; keep the heading.
Never
- A clickable current page.
- A breadcrumb as the only navigation — it is orientation, not a menu.
- A separator carrying meaning (
>vs/implying something). It isaria-hidden; nobody hears it.