Sidebar
A sidebar built from composable parts. Drag its edge to resize, collapse it away, and on mobile it becomes a drawer.
Installation
Base UI flavor. Switch in the right panel.
Playground
Layouts
3 layout options: sidebar default, floating to elevate the sidebar in a higher surface, inset to make your content stand out.
Sidebar
Floating
Inset
Nesting
2 levels of nesting, one section level and one parent level.
Actions & badges
Add badge indicator and up to 3 actions. Actions show on hover so the label keeps maximum readability.
Header & footer
Stack the header and footer content vertically or horizontally.
Vertical stacking
Horizontal stacking
Callouts
Elevate & promote news using the callout with stackable Banners or Inlines.
Stacked inlines
Stacked banners
Resize, collapse & peek
Drag to resize, click to collapse, or press [ key. Open sidebar on click or hover.
On hover
On click
Inside a dialog
A settings dialog with the sidebar inside. 3 props make it fit: collapsible="none" drops the rail and the drawer, persist={false} skips the cookie, and h-full pins it to the dialog's height. Below sm a Select takes over.
No icon rail version?
Hot take baked into this component: there's no icon-only collapsed mode. On purpose.
Icon rails look tidy in screenshots but fail in use. Six ambiguous glyphs, and you suddenly need to tooltip most of them until you've found the right one.
Nesting, section labels and complementary actions are impossible to reflect. It only benefits power users on simple sidebars — and it's your worst way to educate users.
Half a sidebar is confusing for everybody. Convert yours now.
Functional and perfectly aligned
One rhythm everywhere: 24px icon buttons with 16px icons from header to footer.
API Reference — SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled open state — pair with onOpenChange. |
| onOpenChange | (open: boolean) => void | — | Fires when the trigger, rail, or shortcut wants to toggle. |
| defaultOpen | boolean | true | Uncontrolled initial state. Read the sidebar_state cookie in a server layout to restore the last visit. |
| persist | boolean | true | Write the desktop state to the sidebar_state cookie (7 days). Mobile drawer state never persists. |
| peek | "none" | "hover" | "click" | "none" | What the collapsed edge does: an edge strip reveals the sidebar as a floating overlay, on hover or on click — and in hover mode, resting on the trigger peeks it too. Escape or an outside press dismisses; peeking never pins it or writes the cookie. |
| shortcut | string | null | "[" left · "]" right | Bare-key toggle, side-aware; null disables. Focus-scoped: the innermost provider containing focus answers. |
| mobileBreakpoint | number | 768 | Width (px) below which the sidebar becomes a modal drawer. |
| width / widthMobile | string | "16rem" / "18rem" | Rail and drawer widths, also published as --sidebar-width and --sidebar-width-mobile. |
API Reference — Sidebar
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "left" | "right" | "left" | Which edge the rail lives on. The provider mirrors it into the default shortcut — "[" left, "]" right — and the trigger's icon, the rail handle, and the drawer's slide all follow. |
| variant | "sidebar" | "floating" | "inset" | "sidebar" | Transparent rail, elevated floating card, or the inset pairing where SidebarInset becomes the card. |
| collapsible | "offcanvas" | "none" | "offcanvas" | Offcanvas slides the rail away; none renders a static, always-open column. (The icon-rail mode is intentionally not supported.) |
| rail | boolean | true | The built-in resize/collapse handle: drag to resize (160–360px), click to collapse, drag past the minimum to collapse. false hides it; the trigger and shortcut still toggle. |
| railTooltipOpen | boolean | — | Pins the rail's tooltip open (true) or closed (false); undefined leaves it on hover. Dragging always hides it — the collapse demo uses it to spotlight the handle. |
| bordered | boolean | true | The sidebar variant's inner-edge border. |
| SidebarTrigger | ButtonProps | — | Ghost icon button calling toggleSidebar(). Its tooltip carries the shortcut key. |
| SidebarContent viewportClassName | string | — | Extra classes for the scroll viewport — a ScrollArea carrying the scroll-fade mask, with the boundary hairline on its frame. |
API Reference — Sections
| Prop | Type | Default | Description |
|---|---|---|---|
| SidebarGroup collapsible | boolean | false | Turns the group's label into an accordion toggle for everything after it. Hover raises the label's contrast and reveals a chevron. |
| SidebarGroup open / defaultOpen / onOpenChange | boolean · (open) => void | — | Control the accordion, or leave it uncontrolled. |
| SidebarGroupActions | part | — | Clusters 1–3 SidebarGroupAction buttons on the label row. A collapsible label keeps its chevron one gap clear of them. |
| SidebarGroupLabel / SidebarGroupAction render | ReactElement | — | Both accept render / asChild for composition. |
API Reference — Content level 1
| Prop | Type | Default | Description |
|---|---|---|---|
| focusRing | boolean | true | SidebarMenu: draw the traveling keyboard focus ring. Off, keyboard focus moves the hover background only — for menus whose rows are the whole surface, like a settings dialog's section list. |
| SidebarMenuButton isActive | boolean | false | Marks the current row: aria-current, the traveling active background, and the semibold weight shift. |
| SidebarMenuButton icon | IconComponent | — | Leading icon — stroke width animates 1.5 → 2 with the row's state. |
| SidebarMenuButton status | "active" | "unread" | "idle" | — | Leads with a status dot instead of an icon: active/unread fill it, idle rings it. Stamps data-status, adds visually-hidden "unread" text, and active implies isActive. |
| SidebarMenuButton dot | "filled" | "ring" | — | Visual-only dot for when the status vocabulary doesn't fit. Overrides the status-derived dot; ignored when icon is set. |
| SidebarMenuButton size / variant | "default" | "sm" | "lg" · "default" | "outline" | — | Row height (default follows the size ladder; lg is a 48px two-line row) and an outline treatment for standalone rows. |
| SidebarMenuButton render / asChild | ReactElement · boolean | — | Render into a custom element, e.g. render={<Link href=…/>}. |
| SidebarMenuBadge | part | — | Trailing count. Keeps the rightmost slot when the row also has actions. |
| SidebarMenuAction showOnHover | boolean | false | Hide the action until the row is hovered or focused. The row reserves its width only while it shows, so the label runs full width at rest. Tracks the row's own button — a child's hover never reveals it. |
| SidebarMenuActions | part | — | Clusters more than one action on a row and publishes the count, so the row reserves exactly the gutter the cluster needs. |
| SidebarMenu size | "default" | "compact" | — | Pins the menu's rows to one step of the size ladder; omitted, they follow the surrounding SizeProvider. |
| SidebarMenuSkeleton showIcon | boolean | false | Placeholder row while data lands. Widths are deterministic, so SSR and client agree. |
API Reference — Content level 2
| Prop | Type | Default | Description |
|---|---|---|---|
| SidebarMenuSub open | boolean | true | Built-in collapse on the sub-tree's measured height, never an animated auto — wire it to state alongside a toggling row. |
| SidebarMenuSubButton | { size, icon, isActive } | — | Nested row: 24 / 28px tall, text stays at the parent rows' size. Renders an <a> by default; also accepts render / asChild. |
| SidebarMenuBadge / SidebarMenuAction | part | — | Behave as they do at level 1, scoped to the nested row: an action here reveals on its own row, not on its siblings or its parent. |
| Highlight scope | — | — | Each sub-menu runs its own hover/active/focus overlays, so a nested row and its parent never fight over which one is lit. |