C-50 · Components · Navigation
All elements · SideNavigation on the Components page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve SideNavigation
Approving as
Guidance
When to use
- List the app's main destinations in the shell's nav column, where they stay in view on every page.
- Show a hierarchy of up to three levels the user moves through often, such as Cases › Benefit claims › Supporting documents, opening and closing levels with the twisty.
- Give a settings area, a help center or an admin console its own section list beside the content, outside the shell.
-
Mark the page the user is on with
aria-current="page", so the rail doubles as a "you are here".
When to consider something else
- When the views are peers of one record and switch in place without changing the page, use Tabs. See Tabs.
- When the user browses and selects items in a hierarchy (folders, a taxonomy) rather than going to pages, use Tree. See Tree.
- When the user only needs the path back up to the root, add a Breadcrumb above the title instead. See Breadcrumb.
- When the destinations are a handful of secondary links (Help, Settings), put them in the shell's utility strip. See App shell.
Usability
- Keep labels short and in the words of the page titles they lead to.
- Keep the order stable; let users hide, not reorder, items unless the app has an organize mode (nav-tree.css).
- Open the level that holds the current page when a page loads, and leave the others as the user left them.
-
Use
__sectionheadings to split a long rail into named runs instead of nesting deeper. - Use
__badgeonly for a count the user acts on, such as new items in a queue.
- Don't nest more than three levels; deeper paths belong in the page (a Tree or a Breadcrumb).
- Don't make a row with children only a toggle when it is also a page; give it both the twisty and the link.
- Don't put actions that change data (Delete, Archive) in the rail; row actions open a menu.
- Don't hide the current page inside a collapsed level.
Accessibility
Keyboard
| Keys | Action |
|---|---|
| Tab / Shift+Tab | Moves through the twisties, links and row actions in reading order. |
| Enter | Follows the focused link. |
| Enter or Space on a twisty | Opens or closes its level; focus stays on the twisty. |
| Shift+F10 or the context-menu key | In the shell, opens the focused row's menu (nav-tree.css, D-3). |
Roles and names
-
The rail is a
navlandmark with anaria-labelthat says what it lists ("Main", "Settings", "Case management"); use different names when a page has more than one. -
Destinations are links (
a.qf-nav__item); a row that only groups others is abuttonwitharia-expandedandaria-controls. -
Each twisty is a
buttonwitharia-expanded,aria-controlsnaming its__subitems, and anaria-labelof "Expand …" or "Collapse …" with the row's label. - The current page carries
aria-current="page"; only one item does. -
Icons are
aria-hidden; every item keeps a visible text label. A__badgecount needs words for screen readers when the number alone is unclear ("12 open"). -
A collapsed level is
hidden, so its links leave the Tab order and the accessibility tree.
Focus
- Every row part shows the focus ring; on the current row's chrome fill the ring is drawn inset in white (light) or blue-900 (dark) so it keeps 3:1.
- Opening or closing a level never moves focus.
- The page's skip link goes past the rail to the content.
Other requirements
- Item text is 9.03:1 on the page (6.49:1 in dark); the current row is 9.44:1 white on the chrome fill and 14.76:1 on a nested current row.
- Rows are 1.75rem tall, twisties 1.5 × 2rem and row actions 2rem, meeting 2.5.8.
- The current page is marked by a fill and bold text, not color alone, and its parents keep the fill so the path stays visible (1.4.1).
- Long labels end in an ellipsis; give them a tooltip or keep them short so 1.4.10 reflow does not hide meaning.
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (4 rows) |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (6 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- side-navigation --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
| Source |
css/classic/components/side-navigation.csscss/classic/composites/nav-organize.csscss/classic/composites/nav-tree.css
|
|---|---|
| Specimens also use | Box · Card · Chip · Icon · Table · Text · Toolbar |
| Sprites | sprites/icons.svg |
| React | Not yet available: the React binding has not started. |
Latest updates
| Date | Version | Breaking | Description |
|---|---|---|---|
| 2026-10-20 | 0.1 | No |
SideNavigation (C-50) catalogs the shell's .qf-nav rail as a standalone component:
its rules move from composites/shell.css to components/side-navigation.css (rows, twisties,
nested __subitems, aria-current="page" and the fill its parents keep,
__section, __badge), with a specimen outside the shell. The shell
keeps only placement (.qf-shell > .qf-nav: the nav column, clipping, the drawer
layer, the list's room under the masthead) and the fillets that join the current row to the app
bar, now off outside the shell. Element pages credit .qf-nav to SideNavigation
instead of the App shell
|