Components / navigation
Breadcrumb
The W3C ships only two upstream assertions for this pattern, so almost nothing external will catch a mistake here.
Summary
The trail back up a hierarchy.
| APG pattern | Breadcrumb |
| Built on | native elements |
| Status | stable |
| Since | 0.1.0 |
<Breadcrumb items={[
{ label: 'Portfolio', href: '/' },
{ label: 'Case studies', href: '/case-studies' },
{ label: 'Northlight' }
]} />
Anatomy
A <nav data-se="breadcrumb"> holding an ordered list. The last item has no href and becomes current.
| Part | Element | Required |
|---|---|---|
current | <span> | yes |
item | <li> | yes |
list | <ol> | yes |
Properties
| Property | Values | Default | What it does |
|---|---|---|---|
items | BreadcrumbItem[] | required | The trail, root first. The last item is the current page: give it no href, so tabbing through the trail never lands on where the reader already is. |
label | string | Breadcrumb | The nav landmark's accessible name. Change it only if a page has two trails. |
States
| State | Expressed as | |
|---|---|---|
| Hover | :hover |
Behavior
None. It is a list of links.
Accessibility
The current page carries aria-current="page" and is not a link. A trail whose last item links to where you already are is a control that does nothing.
The <nav> is named, so a page with breadcrumbs and site navigation has two distinguishable landmarks.
The separator is decorative and never a text character a screen reader reads. A trail that announces "slash" between every level is four extra words per level.
Keyboard
None of its own. Each link is a tab stop, in trail order.
Roles, states and properties
| Attribute | Set by |
|---|---|
aria-current | always page, set at render |
aria-label | set at render |
Focus
The current page is not a link and takes no focus, so tabbing through the trail never lands on where the reader already is.
Screen reader
Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.
Contrast and target size
npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.
It sets no target size of its own; anything interactive inside it brings its own.
Tested
test/browser/upstream-natives.spec.mjs
Known gaps
_No upstream requirement is recorded as not applicable._
Appearance
| Token | Used for |
|---|---|
--se-space-1 | gap |
--se-space-2 | gap |
--se-text | color |
--se-text-disabled | color |
--se-text-muted | color |
--se-weight-medium | font-weight |
Motion
None.
Content
Each label names the level as the reader would name it. The first is usually the site or the section, not "Home" — Portfolio says more.
Usage
Only worth showing when the hierarchy is real and the reader could plausibly want a level above. A two-item breadcrumb on a flat site is decoration.
Overflow collapse is the only hard part, and this component does not do it: a deep trail wraps rather than collapsing to an ellipsis. If a trail is deep enough to need collapsing, the hierarchy is probably too deep.
When not to use it. As a substitute for a back link. A breadcrumb says where you are in a structure; it does not say where you came from.
The trail takes the eyebrow role. A breadcrumb is a small label above the thing it describes, which is what that role is for — and at the top of a page it has to sit under the title without competing with it. The role brings the narrow face, the small step, the tracking and the uppercase; the component keeps only the row and the separator.
It used to set --se-text-sm and nothing else, so it was the type scale without a role: the size came from the contract and the family, weight and tracking came from wherever it happened to sit.
Notes
The W3C ships only two upstream assertions for this pattern, so almost nothing external will catch a mistake here.