Sidebar

Organism
App navigation rail with collapsible sections and active highlighting. The sidebar on the left of this page is a thin adapter over this very Sidebar component: it feeds the docs' nav tree in and gets the same collapse, accordion, and active-highlight behavior back.

Playground

Every control sets a real prop of SidebarProps. The JSX below updates as you go.
Props
collapsed
boolean
compact
boolean
Frame
pick one
active
number
1
JSX · SidebarProps
<Sidebar active={1} />

Variants

2 examples from src/organisms/sidebar/sidebar.md, each rendered live from its own JSX.
Collapsed rail
<Sidebar
bordered
collapsed
defaultActive="Dashboard"
items={[
{ label: "Dashboard", icon: "layoutGrid" },
{ label: "Inbox", icon: "inbox" },
{ label: "Settings", icon: "settings" }
]}
/>
Error badge
<Sidebar
bordered
defaultActive="Dashboard"
items={[
{ label: "Dashboard", icon: "layoutGrid" },
{ label: "Members", icon: "users" },
{ label: "Security", icon: "shield", badge: "3", badgeError: true }
]}
/>

Anatomy

1
Rail
2
Section label
3
Item
4
Active item
5
Footer
Dimmed parts appear only in some states or variants.

Props

35 props on SidebarProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
5 props
sections
SidebarSection[]
–
Titled sections of nav rows. Use this or the flat items array.
items
SidebarItem[]
–
Flat list of nav rows, wrapped into a single untitled section.
active
string | number
–
The active row (CONTROLLED), by id, by label, or by flat index across all rows. Omit for uncontrolled use.
defaultActive
string | number
–
Initial active row for uncontrolled use (a bare sidebar moves the highlight on press).
onSelect
(item: SidebarItem, index: number, event: GestureResponderEvent) => void
–
Fired with the selected row and its flat index across all sections.
Density
pick one
1 prop
compact
boolean
false
Frame
pick one
4 props
bordered
boolean
false
A rounded card inside the field hairline.
floating
boolean
false
A rounded card lifted on the medium shadow (the bordered card natively).
embedded
boolean
false
Navigation groups embedded in an app-owned rail: no frame, fill or width of its own and no scroll body (the host rail scrolls); the header and footer slots stack above and below the groups as plain blocks.
markers
boolean
false
Trailing marker dots: the accent on the active row, the field hairline on a row of the current place (an item's marked, or every row of a current section).
Collapse axis
4 props
collapsed
boolean
false
Collapsed to the mini icon-rail (CONTROLLED). Omit for uncontrolled use.
defaultCollapsed
boolean
false
Initial collapsed state for uncontrolled use (default expanded).
collapsible
boolean
false
Show the collapse/expand toggle in the header slot.
onToggleCollapse
() => void
–
Fired when the user taps the collapse/expand toggle.
Accordion open-state
4 props
openSections
string | string[]
–
Open collapsible sections (CONTROLLED), by section id/title. A single key by default, an array when independentSections. Pair with onOpenSectionsChange.
defaultOpenSections
string | string[]
–
Initial open sections for uncontrolled use.
onOpenSectionsChange
(value: string | string[]) => void
–
Fired with the next open sections whenever a section header toggles.
independentSections
boolean
false
Allow more than one collapsible section open at once (default: one-open-at-a-time).
Responsive drawer axis
8 props
responsive
boolean
false
Opt in to the responsive rail->drawer behavior. Off by default: a bare sidebar is the inline rail at every width, byte-identical to before.
drawer
boolean
false
Always the drill-down drawer, at every width, never the inline rail: for an app shell whose only entry to the site tree is its own hamburger (a top app bar). Takes the same open / onOpenChange wiring as responsive, and wins over it.
open
boolean
false
The narrow drawer's open state (CONTROLLED). The consumer wires its hamburger to this.
defaultOpen
boolean
false
Initial open state for the uncontrolled narrow drawer (default closed).
onOpenChange
(open: boolean) => void
–
Fired when the narrow drawer opens or closes (scrim tap, back, or a leaf selection).
drawerBreakpoint
BreakpointKey
–
The width at and below which responsive switches to the drawer (default lg = 1024).
drawerWidth
number
–
The narrow drawer panel width in px (default 288).
drawerContentInsetBottom
number
–
Extra bottom padding for the drawer's scrolling content, so its last rows clear persistent chrome painting over the drawer (e.g. a native bottom tab bar on Android).
Responsive drawer EDGE axis
9 props
drawerRight
boolean
false
Slide the drawer in from the end (right) edge instead of the start (left).
drawerTop
boolean
false
Drop the drawer down from the top edge (a top sheet).

Accessibility and keyboard

Keyboard
Tab
Moves between links
Enter
Follows the link
Semantics
Roles
button
navigation
States
expanded
selected
ARIA on web
aria-current
aria-expanded
aria-label
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
On the web a hovered row's wash (the palette's translucent hover) fades in over 150 ms and back out when the pointer leaves, in the rail and the drill-down alike; the pressed and active fills still switch at once, and Reduce Motion makes the wash instant.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Web
control 10 · field 10 · card 16 · Instrument Sans
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

The one Sidebar below is the whole story: on desktop it is the collapsible accordion rail (tap the header chevron to shrink it to icons); narrow the window to a phone and the same component becomes a start-edge drill-down Liquid Glass menu opened by the top bar's hamburger, exactly like this page's own left nav. AppShell is the docs' live app frame (the top bar and placeholder page); the Sidebar wiring is the part you copy.
<AppShell>
{({ open, setOpen }) => (
<Sidebar
responsive
collapsible
defaultActive="Dashboard"
open={open}
onOpenChange={setOpen}
header={(collapsed) => (collapsed ? <Icon layoutGrid primary size={22} /> : <Typography body semibold>Acme</Typography>)}
sections={[
{ items: [
{ label: "Dashboard", icon: "layoutGrid" },
{ label: "Inbox", icon: "inbox", badge: "3" }
] },
{ title: "Reports", icon: "barChart2", collapsible: true, items: [
{ label: "Analytics", icon: "barChart2" },
{ label: "Traffic", icon: "activity" }
] }
]}
/>
)}
</AppShell>
In glass mode the active row paints a control-layer pane behind its icon, label and badge; the drill-down drawer keeps its own row treatment. Solid mode keeps the skin's own row fill.
On the web a hovered row's wash (the palette's translucent hover) fades in over 150 ms and back out when the pointer leaves, in the rail and the drill-down alike; the pressed and active fills still switch at once, and Reduce Motion makes the wash instant.
Add responsive to make it adapt across breakpoints: it stays the accordion rail on desktop, and at and below the lg breakpoint (1024px) it becomes a start-edge (left, RTL-aware) navigation drawer that drills through the same sections one level at a time (tap a group to slide in to its rows, back to return). Drive the drawer's open state with open / onOpenChange from your own hamburger button; the header and footer slots pin above and below the drill-down, just as in the rail. The drawer slides from the start (left) edge by default; drawerRight, drawerTop, and drawerBottom change which edge it slides in from. (This page's own left nav does exactly this: an accordion rail here, a drill-down drawer on a phone.)

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--bg2
--fd
--fm
--ft
--ion-soft
--ion-tx
--ln
--sf2
--sf3
--tx2
--tx3

Related components