A long way here
The trail is an ordered list of links; only the last crumb carries aria-current="page", and it is a link like the others so the current page is reachable by keyboard too.
The trail to the current page, told in links, with the page itself at the end of it.
@pacit/components/breadcrumbpct-breadcrumb, pct-crumb, a[pctCrumbLink]Implements the W3C ARIA APG Breadcrumb pattern ↗
import { Component } from '@angular/core';
import {
PctBreadcrumb,
PctCrumb,
PctCrumbLink,
} from '@pacit/components/breadcrumb';
/** A trail of real links; the current page says so with `aria-current`. */
@Component({
selector: 'demo-breadcrumb',
imports: [PctBreadcrumb, PctCrumb, PctCrumbLink],
template: `
<pct-breadcrumb ariaLabel="You are here">
<pct-crumb><a pctCrumbLink href="#home">Home</a></pct-crumb>
<pct-crumb><a pctCrumbLink href="#library">Library</a></pct-crumb>
<pct-crumb>
<a pctCrumbLink href="#data" aria-current="page">Data</a>
</pct-crumb>
</pct-breadcrumb>
`,
})
export class BreadcrumbDemo {}breadcrumb.demo.ts — the same file runs here and is highlighted under Code
One import per component, and the shortest possible use — the package itself is installed once, on the start page.
import { PctBreadcrumb, PctCrumb, PctCrumbLink } from '@pacit/components/breadcrumb';| Exported | What it is |
|---|---|
PctBreadcrumbcomponentpct-breadcrumb | A breadcrumb: the way here, told in the platform's own links. |
PctCrumbcomponentpct-crumb | One step of the trail: role="listitem", and the separator drawn before its content. |
PctCrumbLinkcomponenta[pctCrumbLink] | The trail's link, standing on the consumer's own anchor. |
<pct-breadcrumb ariaLabel="You are here">
<pct-crumb><a pctCrumbLink href="/">Home</a></pct-crumb>
<pct-crumb><a pctCrumbLink href="/data" aria-current="page">Data</a></pct-crumb>
</pct-breadcrumb>Each example is the component itself, not a screenshot. Open the code under any of them.
The trail is an ordered list of links; only the last crumb carries aria-current="page", and it is a link like the others so the current page is reachable by keyboard too.
Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.
A breadcrumb: the way here, told in the platform's own links.
| Name | Type | What it does |
|---|---|---|
ariaLabeldefault '' | string | The accessible name of the landmark. Overrides texts().breadcrumbLabel, whose job is only to carry a sensible default in the application's language. |
(click), (focus), the form's submit — are the platform's, and two-way models above carry the rest. The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.
| Attribute | Bound to | Note |
|---|---|---|
class | pct-breadcrumb | |
role | navigation | A named navigation landmark — the pagination's reasoning one component over: the landmark is how a screen-reader user reaches the trail and steps over it, and it has to be named because two trails on one page are two landmarks. |
aria-label | ariaLabel() || texts().breadcrumbLabel |
Two ways in, both stable. No class names to depend on.
In a test, PctBreadcrumbHarness, PctCrumbHarness, PctCrumbLinkHarness from @pacit/components/testing read a part by the name below and a state by its attribute — the harness gate holds the names to the package.
| Part | Selector | What it is |
|---|---|---|
list | [data-pct-part="list"] | the ordered list of crumbs |
separator | [data-pct-part="separator"] | the mark between two crumbs |
Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.
| Token | Styles |
|---|---|
--pct-breadcrumb-fg{pct.text}#0f172a · #ffffff | Text colour of the breadcrumb. |
--pct-breadcrumb-font-size{pct.font.size.sm}13px | Font size of the breadcrumb. |
--pct-breadcrumb-gap{pct.space.2}4px | Gap between crumbs. |
--pct-breadcrumb-link-fg{pct.text-muted}#334155 · #e2e8f0 | Text colour of a link. |
--pct-breadcrumb-link-fg-hover{pct.text}#0f172a · #ffffff | Text colour of a link on hover. |
--pct-breadcrumb-link-font-weight-current{pct.font.weight.medium}500 | Font weight of the link naming the current page. |
--pct-breadcrumb-link-target-min{pct.target.min}24px | Touch-target floor of a link. |
--pct-breadcrumb-separator-fg{pct.text-muted}#334155 · #e2e8f0 | Text colour of the separator. |
A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.
[data-theme='brand'] {
--pct-breadcrumb-link-fg: #0f766e;
--pct-breadcrumb-link-fg-hover: #134e4a;
--pct-breadcrumb-separator-fg: #5eead4;
} Pattern: Breadcrumb — (0054) a named navigation landmark, a list of links, aria-current="page" on the one you are on. Named in the class JSDoc.
| key | effect | test |
|---|---|---|
Tab / Shift+Tab | moves between the steps | apps/sandbox-e2e/src/breadcrumb.spec.ts — "every step is a real tab stop" |
Enter | follows the link | none — deliberately: the anchor navigates on its own, and testing it tests the browser (req-api-platform) |
There are no handlers to map: nothing here listens to a key.
Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.
libs/components/breadcrumb/src/breadcrumb.tsapps/sandbox-e2e/src/a11y.spec.ts — /breadcrumb in SBX_ROUTES; the audit put the target floor into the stylesheet (lesson-139)apps/sandbox-e2e/src/visual.spec.ts — breadcrumb-pattern and breadcrumb-pattern-rtlforced-colors: active — no state carried by colour alonelibs/components/breadcrumb/src/link.scss — links LinkText, the current step a weight, the separator CanvasText. The reading: apps/sandbox-e2e/src/forced-colors.spec.tsprefers-reduced-motion — duration from a tokennot applicable — no motion at alllibs/components/breadcrumb/src/link.scss — min-block-size: var(--pct-breadcrumb-link-target-min) on every link, measured in by the axe audit rather than believed--pct-control-height-*deliberately not: the trail is typography, not a control — the badge's argument one component over; it is sized by its own type tokendir="rtl" screenshotapps/sandbox-e2e/src/rtl.spec.ts — the trail mirrors and every separator turns (:dir(rtl), the calendar's selector). The screenshot: breadcrumb-pattern-rtlNG05xxapps/sandbox-e2e/src/hydration.spec.ts — /breadcrumb in SBX_ROUTESlibs/components/parts.snapshot.md — list, separatorcontrast.policy.jsonlibs/tokens/src/contrast.policy.json — four entries: both link states and the current word against the page (AA error), the separator's edge (UI warn — decorative, and exempt is no reason to stop measuring); libs/tokens/tokens.snapshot.mdPCT_TEXTSlibs/components/core/src/texts.ts — breadcrumbLabel, read at render timelibs/components/size.snapshot.md — the ./breadcrumb rowdocs/acr/at/ — three readers, whole sandbox, and the current page is announced as current, by name: Data · link. · (Current page) (Orca), Data, link, current page (NVDA). The landmark and the list are announced on entry — navigation · Breadcrumb · List with 3 items (Orca) — so the trail is a list inside a named navigation to a reader, which is the whole promise. VoiceOver leaves this view after one stop: on 2026-09-16 it read the list on the first link, and in the pass of 2026-10-29 only its usage hint thereapps/sandbox/src/app/views/breadcrumb/ (the sandbox view). The published site: /components/breadcrumb — prerendered, the demo's own source is the code tablibs/components/breadcrumb/src/breadcrumb.spec.ts — 12 cases; libs/components/mutation.snapshot.md — breadcrumb.ts measured in the day's shared full runRead at build time from the tracked snapshots, not typed here.
pct-menu owned by the consumer, and it returns as its own decision the day a real application shows the need.items input. An array of {label, url} would regenerate anchors the router already owns — the markup is the consumer's, which is the component's founding split.[aria-current], and bare text carries no attribute; it is told apart by colour in the author palette and by not-being-a-link in forced colours — a consumer who wants the weight writes the current step as a link to itself.