On this page
Nav released
A list of links to an application's screens, each with an icon and a name, and the current one marked — at the side on a wide screen and in a drawer on a narrow one, wherever the application puts it.
- Import
@pacit/components/nav - Selectors
nav[pctNav], pct-nav-item, a[pctNavLink]
No APG pattern applies, and none is invented — the reasoning is under Accessibility ↓
import { Component, signal } from '@angular/core';
import { PctNav, PctNavItem, PctNavLink } from '@pacit/components/nav';
/**
* A navigation of three screens, the current one marked by aria-current. The preview renders
* with no router, so a press only moves the mark; an application's links are `routerLink`.
*/
@Component({
selector: 'demo-nav',
imports: [PctNav, PctNavItem, PctNavLink],
template: `
<nav pctNav aria-label="Screens" style="inline-size: 200px">
@for (screen of screens; track screen) {
<pct-nav-item>
<a
pctNavLink
[href]="'#' + screen"
[attr.aria-current]="current() === screen ? 'page' : null"
(click)="$event.preventDefault(); current.set(screen)"
>{{ screen }}</a
>
</pct-nav-item>
}
</nav>
`,
})
export class NavDemo {
protected readonly screens = ['Rooms', 'Devices', 'Settings'];
protected readonly current = signal('Devices');
}nav.demo.ts — the same file runs here and is highlighted under Code
Usage
One import per component, and the shortest possible use — the package itself is installed once, on the start page.
import { PctNav, PctNavItem, PctNavLink } from '@pacit/components/nav';| Exported | What it is |
|---|---|
PctNavcomponentunreleasednav[pctNav] | An application's navigation: a list of links to its screens, the current one marked. |
PctNavItemcomponentunreleasedpct-nav-item | One screen of the navigation: role="listitem", the step pct-crumb is for the breadcrumb. |
PctNavLinkcomponentunreleaseda[pctNavLink] | The navigation's link, on the consumer's own anchor: an icon, the screen's name, and the mark of the current screen when the anchor carries aria-current. |
<nav pctNav aria-label="Screens">
<pct-nav-item>
<a pctNavLink routerLink="/rooms" routerLinkActive ariaCurrentWhenActive="page">Rooms</a>
</pct-nav-item>
</nav>Every state, running
Each example is the component itself, not a screenshot. Open the code under any of them.
What it reads, and what it writes back
Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.
unreleased marks 3 of them: on main, and not in @pacit/components@0.2.0 — the next release names the version.
An application's navigation: a list of links to its screens, the current one marked.
Inputs
| Name | Type | What it does |
|---|---|---|
| No inputs — the element is configured by its content. | ||
Outputs
(click), (focus), the form's submit — are the platform's, and two-way models above carry the rest. What it writes on the element
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-nav |
Parts to select, tokens to override
Two ways in, both stable. No class names to depend on.
Parts
In a test, PctNavHarness, PctNavItemHarness, PctNavLinkHarness 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 role="list" inside the landmark |
icon | [data-pct-part="icon"] | the region of the link's icon, at its start |
label | [data-pct-part="label"] | the region of the screen's name |
Tokens
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-nav-gap{pct.space.2}4px | Gap between the links. |
--pct-nav-icon-color{pct.text-muted}#334155 · #e2e8f0 | Colour of a link's icon. |
--pct-nav-link-bg-current{pct.primary-100}#dbeafe · #1e40af | Background of the link to the current screen. |
--pct-nav-link-bg-hover{pct.surface-100}#f1f5f9 · #1e293b | Background of a link under the pointer. |
--pct-nav-link-fg{pct.text}#0f172a · #ffffff | Text colour of a link. |
--pct-nav-link-fg-current{pct.on-primary-100}#1d4ed8 · #bfdbfe | Text colour of the link to the current screen. |
--pct-nav-link-font-weight-current{pct.font.weight.medium}500 | Font weight of the link to the current screen. |
--pct-nav-link-gap{pct.space.3}8px | Gap between a link's icon and its name. |
--pct-nav-link-height{pct.control.height.md}36px | Minimum height of a link. |
--pct-nav-link-padding-x{pct.space.4}12px | Inline padding inside a link. |
--pct-nav-link-radius{pct.radius.md}8px | Corner radius of a link. |
--pct-nav-marker-color{pct.primary}#2563eb · #60a5fa | Colour of the bar at the start edge of the link to the current screen. |
--pct-nav-marker-inset{pct.space.3}8px | How far the bar marking the current screen stands in from the link's top and bottom. |
--pct-nav-marker-width3px | Width of the bar marking the current screen. |
Theming
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-nav-link-bg-current: #ccfbf1;
--pct-nav-link-fg-current: #115e59;
--pct-nav-marker-color: #0f766e;
}Measured, not assumed
Pattern: none — the APG has no pattern for a site's navigation beyond the navigation landmark, which is the consumer's <nav>; inside it a role="list" and a listitem per link, the shape the APG's own examples write, and aria-current="page" on the current screen. Named in the class JSDoc.
Keyboard
| key | effect | test |
|---|---|---|
Tab / Shift+Tab | moves from link to link, in the list's order | apps/sandbox-e2e/src/frame.spec.ts — "Tab walks the bar in its reading order, then the navigation" |
Enter | on a link, follows it | the same case; the following is the platform's |
There are no handlers to map: every link is an anchor and answers the keyboard on its own. A roving tab stop would be the menu's pattern, and a navigation is not a menu.
What is measured, and what is not yet
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/nav/src/nav.tsapps/sandbox-e2e/src/frame.spec.tsapps/sandbox-e2e/src/a11y.spec.ts — /frame in SBX_ROUTESapps/sandbox-e2e/src/visual.spec.ts — frame-app and frame-app-rtlforced-colors: active — no state carried by colour alonelibs/components/nav/src/nav-link.scss — the current screen is a bar and a weight besides the tint; the bar Highlight, the links LinkText. The reading: apps/sandbox-e2e/src/forced-colors.spec.tsprefers-reduced-motion — duration from a tokennot applicable — the navigation moves nothing; the drawer it may stand in follows its own cardlibs/tokens/src/component.nav.json — a link's height is --pct-control-height-md, 36 px (32 px compact), over the whole width of the row--pct-control-height-*libs/tokens/src/component.nav.json — --pct-nav-link-height is the md control heightlibs/tokens/src/component.nav.json — the link's padding and the gap inside it stand on space.3 and space.4, which the compact scope re-points, and its height on control.height.md, which it lowers; the gap between links stands on space.2, which the compact scope leavesdir="rtl" screenshotlogical properties only, held by check-styles; the screenshot frame-app-rtl, where the bar of the current screen stands at the rightNG05xxapps/sandbox-e2e/src/hydration.spec.ts — /frame in SBX_ROUTESlibs/components/parts.snapshot.md — the three parts abovecontrast.policy.jsonlibs/tokens/src/contrast.policy.json — a link at rest, under the pointer and current (AA error), the bar of the current screen (UI error), the icon (UI warn); libs/tokens/tokens.snapshot.mdPCT_TEXTSnot applicable — the component writes no word of its ownlibs/components/size.snapshot.md — the ./nav rowdocs/acr/at/ — three readers, whole sandbox. The landmark, its name, the count and the current screen: Orca on entering navigation · Screens · List with 4 items · Rooms · link., then Devices · link. · (Current page); NVDA Screens, navigation landmark on the first link — without the link's own name — then Devices, link, same page, current page. VoiceOver reached the bar's Sign out and lost the page before the navigation, so it has not read itapps/sandbox/src/app/views/frame/ (the sandbox view). The published site: /components/nav, its demos' source in the code tablibs/components/nav/src/nav.spec.ts; libs/components/mutation.snapshot.md — nav.ts, measured by a narrow runWhat the repository holds for this component
Read at build time from the tracked snapshots, not typed here.
Known limitations
- No groups and no nesting. One flat list of screens; a heading between groups, or a screen with screens under it, is not drawn. A second
nav[pctNav]under a heading of the application's is a group. - No rail. There is no narrow form of icons alone between the column and the drawer.
- A forgotten
aria-currentis silent. A page with no current screen is legitimate, so nothing can warn about a link the consumer forgot to mark. - NVDA did not say the first link's name. On entering the navigation in the walk of 2026-11-02 NVDA read
Screens, navigation landmarkand stopped; the other links it read whole. - It does not close the drawer it stands in. A link pressed in a drawer leaves it open until the application closes it.