Sidebar nav

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.

Grouped navigation

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.

Sidebar nav · Grouped

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.

Anatomy

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.

PartElementRequirementRule
Rootnav.ds-sidebar-navRequiredNamed navigation landmark; parent owns surface, boundary, width, and placement
Groupdiv.ds-sidebar-nav__group1 or moreRelated destinations; never another nav landmark
Group labelp.ds-sidebar-nav__group-labelOptionalGroup label used only when it improves scanning
Listul.ds-sidebar-nav__listRequiredUnordered destinations; order expresses scan priority
Linka.ds-sidebar-nav__linkRequiredNative destination; current route carries aria-current="page"
Iconds-sidebar-nav__iconOptionalCanonical and aria-hidden beside a visible label
Destination labelds-sidebar-nav__destination-labelRequiredExplicit mount for the destination-name slot; never the group-label selector
Trailingds-sidebar-nav__trailingOptionalNon-interactive metadata; the composed Badge owns its variant
Disclosuredetails.ds-sidebar-nav__disclosureOptionalOne parent category revealing a small related destination set
Disclosure triggersummary.ds-sidebar-nav__disclosure-triggerWith disclosureNative toggle only; never also a destination
Nested listds-sidebar-nav__nested-listWith disclosureOne level of child links aligned beneath the parent label

States

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.

ExampleStateDriverRolesUsage
ProjectsResttext.primaryAn available destination
ProjectsHover:hovercontrol.bg-hovertext.primaryPointer preview
ProjectsActive:activecontrol.bg-activetext.primaryPress confirmation
ProjectsFocus:focus-visiblefocus.ringShared offset ring around the whole row
ProjectsCurrent[aria-current="page"]selection.bgtext.primaryThe current route — linked, with no weight or metric change

Usage notes

SidebarNav provides persistent wayfinding and nothing beyond it.

VerdictRule
DoGive the landmark a concise accessible name, and distinctly name every navigation landmark when more than one exists.
DoUse visible group labels only when they improve scanning; keep them short, noun-like, and connected to the following list.
DoKeep 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.
DoKeep icons decorative and trailing content concise and non-interactive; compose Badge and let it own the needed contrast variant.
DoUse one native disclosure when a category such as Settings contains a small related destination set; keep every child a native link.
Don’tAdd a panel, border, radius, width, or positioning to SidebarNav; its parent surface owns those contextual decisions.
Don’tUse buttons for destinations or nest controls inside a destination link; commands belong outside the navigation list.
Don’tTurn groups into duplicate nav landmarks or separate every group with Divider; the label and root gap already provide hierarchy.
Don’tMake the whole SidebarNav collapsible or transient, or combine navigation and disclosure in one row; Sheet and AppShell own the outer handoff.