Sidebar
App navigation that expands, collapses to an icon rail and becomes a drawer on phones.
Home
Installation
Run it from the root of a project already set up with zunoui init.
npx zunoui@latest add sidebarDependencies
What add sidebar installs alongside the component. The CLI skips any you already have in a compatible version.
ZUNO registry
@zuno/utils@zuno/button@zuno/drawer@zuno/tooltip
npm
@base-ui/[email protected]
Examples
3 examples · 1 componentHome
App shell
Header, grouped links with a badge, a footer and the content column; the trigger or ⌘B slides it away. Narrow the window to see the phone drawer.
Home
Icon rail
collapsible="icon" keeps the icons; hover or focus one to see its tooltip.
Profile
Nested pages
SidebarMenuSub lists child pages under their section.
API reference
Forwards props and ref to the container element; it only adds composition utilities.
| Prop | Type | Default |
|---|---|---|
SidebarProviderState for the desktop panel (open/onOpenChange to control or persist it) and the phone drawer. shortcut ("b") toggles with ⌘/Ctrl; false turns it off. | div | defaultOpen = true |
SidebarA nav landmark (aria-label defaults to "Main"). side is logical and flips in RTL. collapsible: "offcanvas" slides away, "icon" keeps an icon rail, "none" stays open. Below 768 px it becomes a modal drawer. | nav | side = "start", collapsible = "offcanvas" |
SidebarTriggerGhost icon button with aria-expanded; opens the drawer on phones. | Button | "Toggle sidebar" |
SidebarInsetThe content column. render={<div />} when the page already has a main. | main | — |
SidebarHeader / SidebarContent / SidebarFooterTop, scrolling middle and bottom areas. | div | — |
SidebarGroup / SidebarGroupLabel / SidebarSeparatorSections with a label; the label becomes screen-reader-only in the icon rail. | div | — |
SidebarMenu / SidebarMenuItemThe list of destinations. | ul / li | — |
SidebarMenuButtonIcon first, label in a <span>. render={<a href />} for links; isActive marks the current page with aria-current="page". tooltip shows beside it in the icon rail. | button | size = "default" |
SidebarMenuBadgeA count inside the button, read as part of its name. | span | — |
SidebarMenuSub / SidebarMenuSubItem / SidebarMenuSubButtonNested destinations; left out of the icon rail. | ul / li / button | — |
useSidebar()state, open, setOpen, openMobile, setOpenMobile, isMobile and toggle. | hook | — |
States
The states that apply to this component and their visual treatment.
| State | Treatment |
|---|---|
| Expanded | 16 rem panel with icons and labels. |
| Collapsed (offcanvas) | Width 0 and inert; content takes the full width. |
| Collapsed (icon) | Icon rail; labels are screen-reader-only and shown as tooltips. |
| Active | Accent surface, medium weight and aria-current="page". |
| Hover / Focus visible | Accent surface; 2 px sidebar-ring outline inside the button. |
| Phone | Modal drawer from the start edge; swipe, Escape or the backdrop close it. |
Accessibility
The sidebar is a named nav landmark and every menu is a list, so screen readers can jump to it and hear how many destinations it holds.
Links are real links (render={<a href />}); the current page carries aria-current="page", not only a background.
The trigger is a labelled button with aria-expanded. ⌘B / Ctrl B is a shortcut on top of it, never the only way.
Collapsed to offcanvas, the panel is inert, so its links leave the Tab order. In the icon rail labels stay in the DOM as the accessible name and a tooltip shows them on hover and focus.
On phones it is a modal drawer: focus moves inside, Escape or the backdrop closes it, focus returns to the trigger, and choosing a destination closes it.
side is logical: start is the right edge in right-to-left pages, and the panel icon mirrors.