Sections of navigation items for an application shell, on the brand chrome or on the page surface, with an app mark that survives the collapse to a 60px icon rail. Groups expand, or fly out beside the collapsed rail, badges count, and the accent marks the current page. Reach for it as the leading navigation rail of an app.
The rail is a nav landmark named Primary, and its items sit in one list per section so a reader hears how many there are. The active item carries aria-current="page" and a stronger wash than the hover. This is navigation, not an application menu, so there is no roving tabindex and no arrow-key map.
Three grounds, all neutral. brand is the chrome from the theme primary, the same in both modes: deep under light ink, or a light primary under dark ink. surface matches the content cards. canvas recesses the rail onto the app background so the content floats over it. The current item wears a stronger wash than a hover.
variant="flush" squares the corners and rules only the trailing edge, for a rail that butts against a toolbar and the content. The default card keeps its rounding, which is right when the rail stands alone with space around it. Squaring is what stops the rounded corners cutting wedges out of the shell.
The icon slot shows in both states, so the mark is there when the labels go and a 60px rail still reads as an application. The wordmark in brand goes. A collapsed item keeps its name through aria-label and shows a capsule on hover and on focus, which Escape dismisses. collapsed-badges="dot" keeps the count.
An item with children becomes the group's disclosure button: it takes aria-expanded, opens on Enter, and emits suiExpandedChange with the open keys rather than suiNavigate. On the collapsed rail the same button opens a flyout of the children beside the icon. A disabled item is an inert button whatever href says.
The header slot holds the one accent action, a search box or a workspace switcher, and swaps for header-collapsed on the icon rail. A section marked bottom drops to the foot of the list, which is where Settings and Help belong. The footer slot shares its strip with the collapse control.
Long labels wrap onto a second line at the default width. Set width to give them a line of their own, and collapsed-width for the icon rail. Leave both unset and the rail keeps the --sidebar-w token, so a page that retunes that token keeps its own measure.
The rail writes collapsed itself, so it emits suiOpen after it expands and the cancelable suiClose before it collapses. Set collapse-control="none" when the shell owns the toggle, then drive it with the collapsed property or toggle(). The button below also answers the bracket key, which is how an app binds a shortcut.
WorkspaceDana WhitfieldDW
Expanded, on Projects. Press the bracket key with the demo focused.
Moves through the header, every item, the footer and the collapse control in document order. Nothing is skipped, because the rail keeps no roving tabindex.
Enter
Follows the focused link, activates the focused item (suiActiveChange when it was not active, then suiNavigate), opens and closes the focused group, or operates the collapse control.
Space
Activates the focused item, group or control. On an item with href the browser scrolls the page, as it does for any link.
API
<sui-side-nav>
Properties
Property
Attribute
Type
Default
Description
active
active
stringstring | undefined
Key of the active item, its own key or its label. Mutable; updated on item activation and reported through suiActiveChange.
collapseControl
collapse-control
"footer" | "none"
'footer'
Where the rail's own collapse control sits. footer is the default: one button in the foot of the rail, always visible, never hidden behind a hover, in the same place whether the rail is expanded or collapsed. none removes it, for a shell that toggles the rail from its own header instead; write collapsed or call toggle() from there.
collapsed
collapsed
boolean
false
Icon-only mode, 60px wide unless collapsedWidth says otherwise. Mutable; the rail's own control toggles it. A collapsed item keeps its name through aria-label and shows a label capsule on hover and focus; its badge goes, or becomes a dot, as collapsedBadges says.
collapsedBadges
collapsed-badges
"dot" | "hide"
'hide'
What a badge becomes on a collapsed item, where there is no room for the count. hide drops it, which is the default. dot draws a marker on the icon and reads the count into the item's aria-label, so the signal survives the collapse.
collapsedWidth
collapsed-width
numbernumber | undefined
Collapsed width in px. Unset keeps the 60px icon rail.
expanded
property only
string[]
[]
Keys of the open groups, spelled as on every element that holds an outline (sui-tree, sui-tree-table, sui-panel-menu, sui-org-chart). Mutable; the element updates it as parents toggle, and opens the group that holds active when a write from script moves active into a closed one. A row chosen in the collapsed rail's flyout leaves its group closed, the parent marked as holding the current page. Property, not attribute.
height
height
numbernumber | undefined
Rail height in px. Leave it unset in an application shell and the rail fills the row it sits in, which is what variant="flush" is for; a demo on a page sets a number instead.
sections
property only
SuiSideNavSection[]
[]
Sections to render: { heading?, divider?, bottom?, items: [{ label, icon?, badge?, href?, target?, disabled?, children?, command? }] }[]. Items are real links when they carry href and buttons with role="link" otherwise, in document order inside one list per section, with no roving tabindex and no arrow-key map: this is site navigation, not an application menu. Property, not attribute.
tone
tone
"brand" | "canvas" | "surface"
'canvas'
Which ground the rail paints, in every theme and in both light and dark mode. canvas is the default: the app background, one step recessed from the content, so the cards float above the rail the way Linear, Notion and VS Code lay out. surface is the same ground as the content cards, under a hairline. brand is the chrome built from the theme's primary: a deep tone under light ink when the primary is dark, the primary itself under dark ink when it is light, and the same in both modes; a quiet control inside it needs its own inverse tone. Anything else falls back to canvas.
variant
variant
"card" | "flush"
'card'
Shape of the rail. card is the default standalone box: rounded corners, and a border on the light tones, for a rail that sits on a page with space around it. flush squares the corners and keeps a single rule down the trailing edge, for a rail that butts against a toolbar and the content in an application shell. Anything else falls back to card.
width
width
numbernumber | undefined
Expanded width in px. Unset keeps the --sidebar-w token, so a page that already retunes that token keeps its width. Set it when one rail needs a width of its own, for long labels.
Events
Event
Detail
React
Vue
Description
suiActiveChange
string
onSuiActiveChange
@sui-active-change
Fires whenever the element writes active, a choice of an item that was not active, before suiNavigate; detail is the new active. Writing active from script emits nothing. Does not bubble.
Fires before the rail collapses itself; detail is 'trigger' (its own control and toggle() are the only ways it collapses). Cancelable: preventDefault() keeps the rail expanded. It does not bubble.
suiExpandedChange
string[]
onSuiExpandedChange
@sui-expanded-change
Fires after a group parent opens or closes, or when a script write moves active into a closed group and the element opens it; detail is the new expanded array of open keys. Group expansion is indexed state, not open state, so it reports the way sui-accordion and sui-panel-menu do. Does not bubble.
suiNavigate
string
onSuiNavigate
@sui-navigate
Fires when an item is activated; detail is the item key, its own key or its label.
suiOpen
void
onSuiOpen
@sui-open
Fires after the rail expands itself. It does not bubble, as no open-state event does.
Methods
Method
Signature
Description
setFocus()
setFocus() => Promise<void>
Moves keyboard focus to the active item, or to the group that holds it while that group is closed, else the first enabled item.
toggle()
toggle(next?: boolean) => Promise<void>
Collapses or expands the rail from outside, reporting exactly as the rail's own control does: suiOpen after it expands, the cancelable suiClose before it collapses. Pass nothing to flip the current state. This is the hook for an application that binds its own keyboard shortcut or puts the toggle in its header, which is why the element binds no global key of its own.
Slots
Slot
Description
brand
The wordmark beside the mark, expanded only. It goes when the rail collapses; the icon slot is what carries the branding there.
footer
Footer content when expanded (typically a persona row).
footer-collapsed
Shown instead of footer when collapsed (typically an avatar); omit to hide the footer content. The collapse control keeps its place either way.
header
Row under the brand, above the items: a search box, a workspace switcher, or the one accent action.
header-collapsed
Shown instead of header when collapsed (typically one icon button); omit to hide the header row.
icon
The workspace or product mark, shown in BOTH states: beside the wordmark when expanded, alone and centered on the icon rail. Draw it around 28px square.
Types
SuiCloseReasontypeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.
SuiSideNavIteminterfaceOne rail item: label, optional icon class and badge count.
label
string
key?
string
Identity for active and suiNavigate. Defaults to the label; set it once the labels are translated or edited, so a handler keys off something that does not move.
icon?
string
badge?
string | number
href?
string
Renders the item as a real link, so middle-click and open in a new tab work and Enter follows it. suiNavigate still fires and active still updates on the click.
target?
string
Link target for an item with href, e.g. _blank. A _blank item gets rel="noopener".
disabled?
boolean
Inert item, for a destination the account cannot reach. Renders a disabled button whatever href says, emits nothing and takes no aria-current.
children?
SuiSideNavItem[]
Child items, one level deep. The parent stops being a destination and becomes the group's disclosure button: it carries aria-expanded, opens and closes on Enter or Space, and emits suiExpandedChange rather than suiNavigate. On the collapsed rail it opens its children in a flyout beside the icon instead, and the rail stays collapsed. href on such an item is ignored.
command?
() => void
Runs when the item is chosen, alongside suiNavigate. A group parent never runs it.
SuiSideNavSectioninterfaceOne rail section: optional eyebrow heading plus its items.
heading?
string
items
SuiSideNavItem[]
divider?
boolean
Draws a hairline above the section. A collapsed rail already rules between headed sections, so this is the way to separate two sections that carry no heading.
bottom?
boolean
Pushes this section and everything after it to the foot of the rail, above the footer slot. This is where Settings and Help live. Only the first section that sets it takes the space.
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.