Persistent vertical navigation for application destinations, organized into optional labeled groups and one-level disclosures. It owns one named navigation landmark, its links and native expansion state — while the parent Sidebar, AppShell, or Sheet owns the surrounding surface and boundary.
Groups make a longer destination set scannable without creating nested navigation landmarks. The current route remains a native link with aria-current="page"; icons reinforce visible labels, and trailing content stays concise and non-interactive.
Click any destination to move the single aria-current="page" state without navigating. Only the intrinsic labels, groups, and links render here; AppShell or Sheet supplies every surrounding surface, boundary, and height.
The landmark is the component root. Groups and lists provide structure without multiplying landmarks, while icons and trailing metadata remain subordinate to the visible destination label.
| Part | Element | Requirement | Rule |
|---|---|---|---|
| Root | nav.ds-sidebar-nav | Required | Named navigation landmark; parent owns surface, boundary, width, and placement |
| Group | div.ds-sidebar-nav__group | 1 or more | Related destinations; never another nav landmark |
| Group label | p.ds-sidebar-nav__group-label | Optional | Group label used only when it improves scanning |
| List | ul.ds-sidebar-nav__list | Required | Unordered destinations; order expresses scan priority |
| Link | a.ds-sidebar-nav__link | Required | Native destination; current route carries aria-current="page" |
| Icon | ds-sidebar-nav__icon | Optional | Canonical and aria-hidden beside a visible label |
| Destination label | ds-sidebar-nav__destination-label | Required | Explicit mount for the destination-name slot; never the group-label selector |
| Trailing | ds-sidebar-nav__trailing | Optional | Non-interactive metadata; the composed Badge owns its variant |
| Disclosure | details.ds-sidebar-nav__disclosure | Optional | One parent category revealing a small related destination set |
| Disclosure trigger | summary.ds-sidebar-nav__disclosure-trigger | With disclosure | Native toggle only; never also a destination |
| Nested list | ds-sidebar-nav__nested-list | With disclosure | One level of child links aligned beneath the parent label |
Every row uses native link states. Current changes background and text color only — never weight — so moving aria-current cannot shift label width, icons, or trailing metadata.
| Example | State | Driver | Roles | Usage |
|---|---|---|---|---|
| Projects | Rest | — | text.primary | An available destination |
| Projects | Hover | :hover | control.bg-hovertext.primary | Pointer preview |
| Projects | Active | :active | control.bg-activetext.primary | Press confirmation |
| Projects | Focus | :focus-visible | focus.ring | Shared offset ring around the whole row |
| Projects | Current | [aria-current="page"] | selection.bgtext.primary | The current route — linked, with no weight or metric change |
SidebarNav provides persistent wayfinding and nothing beyond it.
| Verdict | Rule |
|---|---|
| Do | Give the landmark a concise accessible name, and distinctly name every navigation landmark when more than one exists. |
| Do | Use visible group labels only when they improve scanning; keep them short, noun-like, and connected to the following list. |
| Do | Keep every destination a native link and mark exactly the current route with aria-current="page"; preserve the resting weight so selection never shifts the row. |
| Do | Keep icons decorative and trailing content concise and non-interactive; compose Badge and let it own the needed contrast variant. |
| Do | Use one native disclosure when a category such as Settings contains a small related destination set; keep every child a native link. |
| Don’t | Add a panel, border, radius, width, or positioning to SidebarNav; its parent surface owns those contextual decisions. |
| Don’t | Use buttons for destinations or nest controls inside a destination link; commands belong outside the navigation list. |
| Don’t | Turn groups into duplicate nav landmarks or separate every group with Divider; the label and root gap already provide hierarchy. |
| Don’t | Make the whole SidebarNav collapsible or transient, or combine navigation and disclosure in one row; Sheet and AppShell own the outer handoff. |