Sidebar

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.
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.)
Dashboard
RC

Good morning, Rachel

$48,250
Revenue this week
1,204
Active users today
Recent activity
tsx
<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>
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.

Props

SidebarDrillDown

Prop
Description
groups
required
SidebarDrillGroup[]
—
activeIndex
required
number
—
activeSectionKey
required
string | null
—
density
required
Density
—
open
required
boolean
Whether the hosting Drawer is open — drives the reset/pre-drill on each open.
onSelect
required
(item: SidebarItem, index: number, event: GestureResponderEvent) => void
—
onRequestClose
required
() => void
Close the hosting Drawer (after a leaf selection or a root-level back).
contentInsetBottom
number
Extra bottom padding so the last rows clear persistent chrome painting over the drawer (e.g. a native bottom tab bar on Android).
fill
boolean
Fill the panel (a full-height left/right drawer) vs shrink within it (a content-sized top/bottom sheet).

Sidebar

Prop
Description
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.
compact
boolean
Density (pick one; default is the comfortable row).
bordered
boolean
Frame (pick one; default is the flush right-bordered column).
floating
boolean
Frame (pick one; default is the flush right-bordered column).
collapsed
boolean
Collapsed to the mini icon-rail (CONTROLLED). Omit for uncontrolled use.
defaultCollapsed
boolean
Initial collapsed state for uncontrolled use (default expanded).
collapsible
boolean
Show the collapse/expand toggle in the header slot.
onToggleCollapse
() => void
Fired when the user taps the collapse/expand toggle.
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
Allow more than one collapsible section open at once (default: one-open-at-a-time).
responsive
boolean
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.
open
boolean
The narrow drawer's open state (CONTROLLED). The consumer wires its hamburger to this.
defaultOpen
boolean
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).
drawerRight
boolean
Slide the drawer in from the end (right) edge instead of the start (left).
drawerTop
boolean
Drop the drawer down from the top edge (a top sheet).
drawerBottom
boolean
Raise the drawer up from the bottom edge (a bottom sheet).
header
ReactNode | ((collapsed: boolean) => ReactNode)
Optional top slot (a brand lockup / logo). A render function receives the collapsed state so it can show a compact mark in the rail. When `header` or `footer` is set, the panel becomes a pinned header + scrolling body (+ footer).
footer
ReactNode | ((collapsed: boolean) => ReactNode)
Optional bottom slot (pinned below the scrolling sections). A render function receives the collapsed state so it can show a compact (icon-only) footer in the rail.
testID
string
E2E hook forwarded to the root element.
style
StyleProp<ViewStyle>
Outer layout composition only (width/flex within a parent), never a restyle hook.

Do & Don’t

Sidebar

Don’t
Acme
Dashboard
Users
Settings
Click an item: two rows wearing the active background means the nav can't tell you which page you're on.
Do
Exactly one item carries the accent background; clicking moves it so the active page is always unambiguous.