Sidebar

Pro

The application navigation rail. Compose switchers, sections and rows that carry state, then pick the shape: a list, a labeled icon rail, a rail with panels, a peeking rail or a page tree.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add sidebar

Usage#

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

Sidebar is a flex column on a bg-card surface. SidebarHeader and SidebarFooter pin to the top and bottom with a hairline separator, and either can hold a SidebarActions cluster beside its switcher; SidebarContent is the scroll region in between. Inside it, SidebarGroup is a navigation section. Give it a SidebarGroupLabel when it needs a heading, or leave it unlabeled for the primary pages. Each SidebarItem is a row; wrap it in a SidebarItemRow when it carries its own actions. The rail and panel, peek, rich row and page tree parts compose around this same skeleton and are listed in the API reference.

import {  Sidebar,  SidebarHeader,  SidebarSwitcher,  SidebarActions,  SidebarContent,  SidebarGroup,  SidebarGroupLabel,  SidebarGroupAction,  SidebarGroupContent,  SidebarItemRow,  SidebarItem,  SidebarItemIcon,  SidebarItemDot,  SidebarItemCount,  SidebarItemActions,  SidebarItemAction,  SidebarCollapsible,  SidebarSeparator,  SidebarFooter,} from "@/components/ui/sidebar"<Sidebar>  <SidebarHeader>    <SidebarSwitcher />    <SidebarActions>...</SidebarActions>  </SidebarHeader>  <SidebarContent>    <SidebarGroup>      <SidebarGroupLabel />      <SidebarGroupAction />      <SidebarGroupContent>        <SidebarItemRow>          <SidebarItem>            <SidebarItemIcon /> {/* or <SidebarItemDot /> */}            <SidebarItemCount />          </SidebarItem>          <SidebarItemActions>            <SidebarItemAction />          </SidebarItemActions>        </SidebarItemRow>        <SidebarCollapsible>          <SidebarItem />        </SidebarCollapsible>      </SidebarGroupContent>    </SidebarGroup>    <SidebarSeparator />  </SidebarContent>  <SidebarFooter>    <SidebarSwitcher />  </SidebarFooter></Sidebar>

Switchers#

SidebarSwitcher is one trigger used for both the workspace switcher (top) and the profile switcher (bottom): a leading visual, a title (with an optional subtitle), and an up/down caret. It renders a real <button>, so it drops straight into a DropdownMenuTrigger asChild. Radix owns the keyboard, focus, and ARIA; the switcher stays lit while its menu is open.

Full switcher#

SidebarSwitcher takes a variant. The default is the compact trigger (a tight gap, a small muted caret, and reduced padding), the everyday switcher, paired with a small leading (an Avatar size="xs", a single initial, or a compact logo tile). Pass variant="full" for the roomier identity row: a wider gap, a heavier title, and room for a subtitle. Pair it with a larger leading (an Avatar size="md", which keeps both initials) for a primary or standalone switcher.

default (compact)
full

Header actions#

SidebarActions puts icon buttons beside a switcher: search and notifications next to the workspace, the way GitBook and Squarespace head their rail. Drop it into SidebarHeader (or SidebarFooter, for help or settings) after the switcher, and the zone lays the two out as a row: the switcher hugs its name so the caret follows it, and the icons sit at the far edge, in line with the ends of the rows below. Fill it with ghost icon-only Buttons; they take the rail’s muted ink at rest and their aria-label as a tooltip.

This is where an app’s global tools live in the docked shell, which leaves the sheet’s header bar for the page. Collapsed to the icon rail, the buttons stack under the switcher’s mark instead of being cut off.

default
collapsed

Items#

A SidebarItem is a <button> by default; pass asChild to wrap an <a> or a Next <Link>. Mark the current page with active (it fills the row and sets aria-current="page"). Lay the children out as <Icon /> Label and push a trailing Badge or count to the right with ml-auto.

Colored icon tiles#

Swap the bare leading glyph for a SidebarItemIcon: a soft, hue-tinted rounded square that gives each destination its own color, the “colored spaces” rail you see in Productboard or Linear. Wrap a single icon and pass a tone (decorative: purple, pink, teal, orange; status: green, blue, red, amber, plus neutral, the default, and brand). The tile scales with density and becomes the centered chip in the collapsed rail. The active row still reads through the neutral chip and ink bar, the tiles decorate, they don’t signal selection.

Nested navigation#

SidebarCollapsible folds a sub-list of routes under a parent row. Give it an icon and label, drop the child SidebarItems inside, and the row gains a caret that toggles them open, built on Radix Collapsible, so it owns the keyboard, focus, and height animation. The sub-list is indented under the label and traced by a guide rail; set active on the parent when one of its child routes is current. In the collapsed icon rail it falls back to a single icon row.

A long sub-list can be split under SidebarGroupLabel sub-headings: inside a sub-list the label stays a plain heading, even when the whole section sits in a collapsible group. With indicator on, folding a branch that holds the current route moves the pill to the first row still on show, so a parent marked active while folded catches it. The docs sidebar on this site is built exactly this way.

Collapsible sections#

Pass collapsible to a SidebarGroup and its SidebarGroupLabel becomes a toggle with a caret. Wrap the rows in SidebarGroupContent so they fold beneath it. Use defaultOpen (or the controlled open/onOpenChange pair) to set the initial state. Unlike nested navigation, this folds a whole flat section rather than revealing sub-routes.

Row states#

Rows carry state, not just selection: unread brings a row to full ink and semibold, muted sinks it, and SidebarItemCount adds a quiet tabular count. Wrap a row in SidebarItemRow to give it hover-revealed SidebarItemActions, and add a SidebarGroupAction after a heading for a section +. In the collapsed rail unread becomes a small ink dot on the glyph, and counts and actions step aside.

A plain count takes the row’s own ink, so a column of totals reads as one quiet figure per row and lifts only where the row is active or unread. Reach for a Badge when the number is a label rather than a count. For a colour where the icon goes (a mail label, a project), use SidebarItemDot: it fills the same 20px glyph column, so dotted and iconed rows line up (the Mail recipe below uses it for labels).

Collapsed (icon rail)#

The rail has two variants, driven by the same markup. The default is the full-width rail; collapsed shrinks it to a 48px icon-only column. Labels go sr-only, each row becomes a centered icon, the active row is marked with a left ink bar instead of a full chip, sections are divided by a short rule, and the switcher reduces to its leading visual. Give every SidebarItem a label so the collapsed row gets a hover tooltip and an accessible name, and mark secondary sections with <SidebarGroup hideWhenCollapsed> so they drop out of the rail rather than reducing to bare glyphs.

default
collapsed

Stacked (labeled icon rail)#

layout="stacked" is the rail Slack and Teams lead with: a 72px column where every row sets its glyph in a rounded tile and keeps a short label underneath. The tile is the whole marker, lit on hover and held while active, and a Badge in the row pins to its corner as the unread count. Like the collapsed rail, headings go sr-only, sections split on a short rule and the switchers reduce to their mark. Set it beside an ordinary rail for the two-pane workspace shell: the stacked rail picks the area, the list rail carries its channels.

Bare stacked#

The stacked rail lights a rounded tile behind the glyph on hover and selection, the Slack read. Pass tile={false} for the bare rail of Teams: no tile at all, the current row carries the ink edge bar and its glyph and label turn full ink. The glyph keeps its padded box either way, so badges and unread dots pin to the same corner.

Tiled
Bare

Rail and panel#

SidebarPanels pairs a narrow icon rail with a 240px panel for the area it selects, as in Supabase, Discord or the VS Code activity bar. The rail is a keyboard-navigable tablist, and each SidebarPanel is an ordinary list Sidebar with its own title and groups. Clicking the open area again folds the panel away (collapseOnReselect); turn that off for a console where the panel is always out.

With collapseOnReselect on (the default) the rail behaves like the VS Code activity bar: click the open view and the panel folds, leaving no lit row and the whole width to the content. Opening and closing animate the panel’s width; switching areas while open is an instant swap. Drive it from anywhere inside with useSidebarPanels().

Peek#

SidebarPeek shows a rail on demand. mode="expand" keeps a 48px icon rail that widens over the page after a short hover or keyboard focus, so the content never moves. Pin it with SidebarPeekPin to dock it; the peek closes on leave, Escape or focus out, and holds while a menu inside it is open.

Two trade-offs are deliberate. While it peeks, the rail keeps its icon-rail form, so headings stay short rules and hideWhenCollapsed groups and nested sub-lists only appear once it is pinned. And pinning reflows the page on purpose: it is the one change that should move the content, so the column tweens to the full width.

Home

Hover the rail, or Tab into it. It opens over the page, so nothing here moves. Pin it to dock it.

mode="offcanvas" hides the rail entirely and slides it in as a floating card from an 8px hot zone on the left edge or from a SidebarPeekTrigger in the content. Resting on the trigger peeks; clicking it docks the rail, and clicking again sends it away. SidebarPeek renders no DOM, so the trigger can live in any top bar.

Home

Hover the left edge or the sidebar button. Click the button, or the pin in the rail, to dock it.

Rich rows#

SidebarRichItem is the same SidebarItem one size up, for rails whose rows are records: leading media, a title with a timestamp, a one-line preview and an unread count. Collapsed, only the avatars or covers remain, with a dot marking unread. The text block is a fixed two-by-two grid, so any part can be left out.

size="lg" is the library row: a 48px cover, square or round by shape, with a SidebarItemMeta placement="center" for a now-playing glyph. SidebarHeaderRow, SidebarTitle and SidebarFilters stack a titled header, a scrolling chip row and a search row in one SidebarHeader.

Page tree#

SidebarTree nests pages to any depth with a 12px step per level. On hover or focus a page’s icon turns into its caret, and its actions (a … menu, a +) appear at the row’s end. Rows match SidebarItem exactly, the keyboard model is the one Tree uses, and clicking a row navigates while only the caret folds.

Q4 launch

Edited Oct 9 by Mara Okonkwo

Scope, owners and the cut line for the October release.

In the collapsed rail the tree shrinks to its top-level pages: each one a glyph with its label as a tooltip, lit while the open page sits anywhere beneath it. A section whose pages mean nothing as bare glyphs should pass hideWhenCollapsed to its SidebarGroup instead, as Favorites does here.

Q4 launch

Edited Oct 9 by Mara Okonkwo

Scope, owners and the cut line for the October release.

Docked (in an app shell)#

A rail standing on its own is a card: it paints bg-card and a hairline against the content beside it. Inside an app shell it shouldn’t: the shell already owns the surface, and a second fill on top of it is the difference between a docked dashboard and a rail bolted onto one. So nesting a Sidebar in a LayoutSidebar turns docked on for you: no fill, no hairline, and the width comes from the shell column, which is also what animates the collapse.

The same applies in LayoutMobileSidebar, where the drawer panel is the surface. Pass docked explicitly to force the treatment outside a Layout, or docked={false} to opt a rail out of the shell it sits in. Combined with floating below, the card treatment wins: that pair is the detached rail floating in the shell gutter.

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Floating (inset)#

floating detaches the rail into an inset card: a full outline, a rounded radius concentric with the app shell, and a shadow that lifts it off the canvas, instead of a single edge hairline. Pair it with padding on the container (or the Layout gutter) so it floats clear of the viewport edges.

Right side#

side="right" docks the rail against the right edge. The separating hairline flips to its left so it reads as an inspector/detail panel beside the main content. Everything else (switchers, sections, density) is symmetric.

Density#

density on Sidebar flows through context to the zone padding, the inter-section gap, the row height, and the switcher padding. compact is the default (app UI); comfortable is roomier. Set it once here or for a whole subtree with DensityProvider. See Density.

Recipes#

Five finished rails built from the parts above, nothing bespoke: each one is plain composition of rows, states, counts and the footer. Lift one whole into an app, or use it as the reference for how the parts combine.

Teams, as in Linear: each team folds its own views under a tinted tile.

Mail: one Compose action, folders with a column of totals, labels as colour dots.

Usage quota: the plan’s meter and its upgrade sit at the foot, above the profile.

Presence: direct messages lead with an avatar carrying its status dot.

User card, as in Discord: the profile, then mute, deafen and settings at one size.

API reference#

Sidebar

The rail: a 256px <aside> landmark (name it with aria-label) that hands density and the collapsed state to every part. Forwards all native <aside> props, and every part accepts className, merged last.

PropTypeDefaultDescription
collapsedbooleanfalseShrinks the rail to a 48px icon-only column. Inherited from an enclosing LayoutSidebar; an explicit value wins.
layout"list" | "stacked""list"stacked is the labeled icon rail of Slack or Teams: a 72px column, each glyph in a rounded tile above a short label, a Badge pinned to the tile's corner.
tilebooleantrueThe stacked rail's selection marker. On, hover and the current row light a rounded tile behind the glyph (Slack); off, the rail goes bare (Teams) and the current row carries the ink edge bar. Only read by layout="stacked".
indicatorbooleanfalseOne highlight that glides to the active row instead of a static chip per row. Ignored while collapsed.
side"left" | "right""left"The edge it docks against; the hairline moves to the inner edge.
floatingbooleanfalseDetaches the rail into a rounded card with a full outline and shadow.
dockedbooleanfalseSits the rail on the app canvas: no fill, no hairline, full width of its column. Inherited from an enclosing LayoutSidebar.
density"compact" | "comfortable""compact"Zone padding, row height and switcher padding. Falls back to the nearest DensityProvider.
Data attributeDescription
data-collapsedPresent while the rail is collapsed.
data-layoutlist or stacked.
data-dockedPresent while the rail is docked.
CSS variableDescription
--surfaceSet to --card (reset when docked) so nested inputs paint the rail's surface.

SidebarHeader

The top zone, with a hairline below, for the workspace switcher; holding a SidebarActions turns it into a row. Forwards all native <div> props.

SidebarContent

The scroll region between header and footer; it measures the active row for the indicator pill. Forwards all native <div> props.

SidebarFooter

The bottom zone, with a hairline above, for the profile switcher; holding a SidebarActions turns it into a row. Forwards all native <div> props.

SidebarActions

A cluster of ghost icon buttons beside the switcher, pushed to the zone's far edge; it stacks under the mark when collapsed. Forwards all native <div> props.

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl""md"The height every control in the cluster takes, so buttons and toggles land on one size instead of each control's own default.
Data attributeDescription
data-control-sizeThe resolved size.

SidebarGroup

A section rendered as a <nav> landmark; name it with aria-label. Forwards all native <nav> props.

PropTypeDefaultDescription
hideWhenCollapsedbooleanfalseDrops the section entirely while the rail is collapsed.
collapsiblebooleanfalseTurns SidebarGroupLabel into a Radix Collapsible trigger that folds SidebarGroupContent. Off while the rail is collapsed.
defaultOpenbooleantrueStarts expanded (uncontrolled); only with collapsible.
openboolean-Open state (controlled); only with collapsible.
onOpenChange(open: boolean) => void-Called when a collapsible group toggles.

SidebarGroupLabel

The section heading: a muted <div>, or in a collapsible group a full-width <button> with a caret that turns on open. Screen-reader only while collapsed.

Data attributeDescription
data-stateopen or closed, on the button of a collapsible group.

SidebarGroupContent

The rows of a section. In a collapsible group it is the Radix Collapsible panel; otherwise an optional plain stack. Forwards all native <div> props.

Data attributeDescription
data-stateopen or closed, in a collapsible group.

SidebarGroupAction

The button at the end of a section heading (Slack's "Channels +"). Place it right after SidebarGroupLabel; it rests hidden and appears while the pointer or keyboard focus is in the section, and sits before the caret of a collapsible group. Dropped in an icon rail. Forwards all native <button> props.

PropTypeDefaultDescription
label*string-The accessible name of the icon-only button ("Add channel").
Data attributeDescription
data-stateopen while a menu or dialog it triggers is open; keeps it on show.

SidebarItem

A nav row, a <button> by default; lay its children out as <Icon /> Label with an optional trailing count. Collapsed, it keeps only the leading glyph. Forwards all native <button> props.

PropTypeDefaultDescription
asChildbooleanfalseRenders the child element (an <a> or Next <Link>) as the row, through Radix Slot.
activebooleanfalseMarks the current page: fills the row, adds the ink bar and sets aria-current="page".
labelstring-The collapsed rail's hover tooltip and accessible name for the row.
unreadbooleanfalseSomething new is waiting: the label goes full ink and semibold without the active chip. In an icon rail a small ink dot marks the glyph's corner instead. Announced as "unread".
mutedbooleanfalseA silenced row: it sinks below the rail's muted ink until hovered or current. Wins over unread.
Data attributeDescription
data-activetrue on the active row.
data-unreadPresent on an unread row (not when it is also muted).
data-mutedPresent on a muted row.

SidebarItemCount

A trailing count, dropped last in a SidebarItem: right-aligned tabular figures in the row's own ink. Gone in an icon rail, and inside a SidebarItemRow it gives its place to the actions while they show. Forwards all native <span> props.

PropTypeDefaultDescription
variant"plain" | "pill""plain"plain is the quiet total; pill the unread capsule of a conversation row.
tone"neutral" | "brand" | "muted""neutral"The pill's fill: neutral (ink) for a thread that wants attention, brand for the accent, muted for a muted one. Ignored by plain.
srLabelstring"unread" for pill, none for plainRead after the number by screen readers, so 3 is announced as "3 unread".
Data attributeDescription
data-variantplain or pill.

SidebarItemRow

Wraps a SidebarItem and its SidebarItemActions side by side (a button can't hold another button) and drives their reveal from its hover and keyboard focus. In an icon rail it renders the row alone. Forwards all native <div> props.

SidebarItemActions

The trailing cluster of a SidebarItemRow, laid over the row's end. Hidden at rest; shown on row hover, keyboard focus in the row, or while a menu it opened is open. Dropped in an icon rail. Forwards all native <div> props.

SidebarItemAction

One small ghost icon button in a SidebarItemActions cluster (a … menu, a +), with a 40px tall hit area. A real <button>, so it drops into DropdownMenuTrigger asChild. Forwards all native <button> props.

PropTypeDefaultDescription
label*string-The accessible name of the icon-only button ("Channel options").
Data attributeDescription
data-stateopen while its menu is open; keeps it lit.

SidebarItemIcon

A soft hue-tinted tile around a single icon, sized by density; decorative (aria-hidden). Forwards all native <span> props.

PropTypeDefaultDescription
tone"neutral" | "brand" | "purple" | "pink" | "teal" | "orange" | "green" | "blue" | "red" | "amber""neutral"The tint and glyph colour.

SidebarItemDot

A colour dot where a row's icon goes (a mail label, a favourite, a project), filling the same 20px glyph column so dotted and iconed rows line up. Decorative (aria-hidden). Forwards all native <span> props.

PropTypeDefaultDescription
tone"neutral" | "brand" | "purple" | "pink" | "teal" | "orange" | "green" | "blue" | "red" | "amber""neutral"The dot's hue.

SidebarCollapsible

A parent row that folds a nested list of SidebarItems beneath it, on Radix Collapsible; collapsed, it falls back to a single icon row. Takes only the props below, no native props.

PropTypeDefaultDescription
label*React.ReactNode-The parent row's label; a string also becomes the collapsed tooltip.
children*React.ReactNode-The nested rows; their edge bar is suppressed.
iconReact.ReactNode-Leading glyph of the parent row.
actionsReact.ReactNode-Trailing content before the caret, such as a count Badge.
activebooleanfalseLights the parent row when one of its child routes is current.
defaultOpenbooleanfalseStarts expanded (uncontrolled).
openboolean-Open state (controlled).
onOpenChange(open: boolean) => void-Called when the section toggles.
disabledbooleanfalseDisables the parent row and its toggle.
classNamestring-Classes for the parent row.
Data attributeDescription
data-stateopen or closed, on the parent row and the nested list.

SidebarSwitcher

The identity row for the workspace or profile switcher, a real <button> that drops into DropdownMenuTrigger asChild and stays lit while the menu is open. Forwards all native <button> props except title.

PropTypeDefaultDescription
title*React.ReactNode-Primary line: the workspace or profile name.
subtitleReact.ReactNode-Secondary line: plan, email or role.
leadingReact.ReactNode-Leading visual: an Avatar, a logo tile or an icon.
caretbooleantrueShows the trailing up and down caret.
variant"default" | "full""default"default is the compact trigger; full the roomier identity row with a semibold title.

SidebarSeparator

A hairline role="separator" between groups. Forwards all native <div> props.

SidebarRichItem

A two-line SidebarItem one size up, for rows that are records (a conversation, a playlist). It is a SidebarItem underneath, so it takes active, label and asChild and every native <button> prop. Put SidebarItemMedia first: the collapsed rail keeps only it.

PropTypeDefaultDescription
size"md" | "lg""md"md is the conversation row (40px avatar, 64px rows); lg the library row (48px cover, 60px rows).
unreadbooleanfalseThe title goes semibold, and in the collapsed rail an ink dot marks the media's corner. Pair it with a SidebarItemCount for the number.
Data attributeDescription
data-unreadPresent on an unread row.
data-sizemd or lg.

SidebarItemMedia

The leading avatar, cover or glyph; a dropped-in Avatar or <img> fills the square. Forwards all native <span> props.

PropTypeDefaultDescription
shape"circle" | "square""circle"circle for people and artists, square (8px corners) for covers, groups and channels.

SidebarItemText

The two-line text block: a fixed two-by-two grid, so its parts can come in any order and any can be left out. Forwards all native <span> props.

SidebarItemTitle

The first line: full ink, one line with an ellipsis, semibold on an unread row. Forwards all native <span> props.

SidebarItemDescription

The second line: a preview or subtitle, muted, one line. Forwards all native <span> props.

SidebarItemMeta

Trailing detail on the right edge: a timestamp, read receipts, a now-playing glyph. Muted and tabular. Forwards all native <span> props.

PropTypeDefaultDescription
placement"top" | "bottom" | "center""top"Beside the title, beside the description, or centred across both lines.

SidebarHeaderRow

One row inside a SidebarHeader that stacks several (the title with its actions, a search and sort row). Forwards all native <div> props.

PropTypeDefaultDescription
hideWhenCollapsedbooleanfalseDrops the row in the collapsed rail.

SidebarTitle

The rail's heading ("Your library"), a 16px semibold <h2>, kept for screen readers when collapsed. Forwards all native <h2> props.

SidebarFilters

A horizontally scrolling chip row under the title, with a fade at its edges. Fill it with a ToggleGroup type="single" size="sm". Hidden when collapsed. Forwards all native <div> props.

SidebarPanels

The rail and panel sidebar: owns the selected area and whether its panel is out, and lays the rail and panels side by side. Forwards all native <div> props.

PropTypeDefaultDescription
valuestring-The selected area (controlled).
defaultValuestring-The area selected on mount (uncontrolled). Set it, so the rail has a tab stop.
onValueChange(value: string) => void-Called when another area is picked.
openboolean-Whether the panel is on show (controlled).
defaultOpenbooleantrueWhether the panel starts on show (uncontrolled).
onOpenChange(open: boolean) => void-Called when the panel opens or closes.
collapseOnReselectbooleantrueClicking the open area again folds the panel (VS Code). Turn it off for a panel that is always out (Supabase).
Data attributeDescription
data-stateopen or closed: whether a panel is out.

SidebarPanelsList

The rail's vertical tablist, in place of a SidebarGroup inside the rail's SidebarContent; name it with aria-label. Up and Down move focus (wrapping), Home and End jump; Enter, Space or a click opens. Forwards all native <div> props.

SidebarPanelsTrigger

A rail row that opens an area's panel: a SidebarItem wired as a tab, lit only while its panel is out. Takes every SidebarItem prop except active.

PropTypeDefaultDescription
value*string-The value of the SidebarPanel it opens.
Data attributeDescription
data-stateopen while its panel is out, else closed.
data-activePresent while its panel is out.

SidebarPanel

One area's panel: a 240px list Sidebar, so the usual parts drop straight in. Takes every Sidebar prop except collapsed and layout.

PropTypeDefaultDescription
value*string-The value of its SidebarPanelsTrigger.
Data attributeDescription
data-stateopen while this panel is on show, else closed.

SidebarPanelHeader

The panel's header, sized to the rail's so the two hairlines meet as one line. Forwards all native <div> props.

SidebarPanelTitle

The area's name, a 16px semibold <h2>. Forwards all native <h2> props.

useSidebarPanels

Reads and drives the panels from anywhere inside SidebarPanels (a hide button, a shortcut): returns { value, open, select, setOpen, toggle }.

SidebarPeek

The state owner for a rail that shows on demand. Renders no DOM, so a trigger anywhere in the content can reach the panel. Opens on a resting mouse or keyboard focus (never touch); closes on leave, Escape or focus out, and holds while a menu inside is open.

PropTypeDefaultDescription
mode"expand" | "offcanvas""expand"expand: a 48px icon rail that widens over the content. offcanvas: a hidden rail that slides in as a floating card.
openboolean-Whether the peek is showing (controlled).
defaultOpenbooleanfalseStart with the peek showing (uncontrolled).
onOpenChange(open: boolean) => void-Called when the peek shows or hides.
pinnedboolean-Docked in the flow (controlled). Inside a Layout, bridge it to the shell's collapsed state so the shortcut and the pin agree.
defaultPinnedbooleanfalseStart docked (uncontrolled).
onPinnedChange(pinned: boolean) => void-Called when the pin or the trigger docks or undocks the rail.
openDelaynumber160How long the pointer rests before the peek shows, in ms.
closeDelaynumber160 (expand), 300 (offcanvas)The grace after the pointer leaves before the peek hides, in ms.

SidebarPeekPanel

The column that holds the rail: a placeholder in the flow plus the floating panel over it. Put a normal Sidebar inside as its only child; in expand mode it stays collapsed until pinned. Forwards all native <div> props.

Data attributeDescription
data-modeexpand or offcanvas.
data-staterest, peek or pinned.

SidebarPeekPin

A ghost icon button for the rail's SidebarActions that docks or undocks it, with aria-pressed. Takes every Button prop except children and iconOnly.

SidebarPeekTrigger

The control in the content that reaches an offcanvas rail: resting on or focusing it peeks, clicking docks, clicking again sends it away. Pass children to replace the glyph. Takes every Button prop except iconOnly.

useSidebarPeek

The peek state for a bespoke control: { mode, open, setOpen, pinned, togglePinned }.

SidebarTree

A role="tree" page tree inside a SidebarGroup that owns the current page, the expansion state and the keyboard (Tree's model). Name it with aria-label. Forwards all native <ul> props.

PropTypeDefaultDescription
valuestring-The current page (controlled): its row takes the active chip and aria-current.
defaultValuestring-The initial current page (uncontrolled).
onValueChange(value: string) => void-Called with the page the user opened (click, Enter or Space on its row).
expandedValuesstring[]-Expanded page values (controlled).
defaultExpandedValuesstring[]-Initially expanded page values (uncontrolled).
onExpandedChange(values: string[]) => void-Called with the full set of expanded values when one folds or unfolds.
linkAsReact.ElementType"a"What a page with an href renders as: pass Next's Link for client routing.

SidebarTreeItem

A page, recursive: nest SidebarTreeItems to give it subpages and a caret. Activating the row navigates; the caret, or Right and Left, folds. Forwards native <div> props to the row, className included.

PropTypeDefaultDescription
value*string-Stable identity for navigation and expansion.
label*React.ReactNode-The page title; a string also names the glyph in the collapsed rail.
iconReact.ReactNode-The page's emoji or glyph. Falls back to a page glyph, so every row keeps its column.
hrefstring-Makes the row a real link, rendered with the tree's linkAs.
actionsReact.ReactNode-Trailing SidebarItemActions, revealed on hover or keyboard focus.
disabledbooleanfalseDisables navigation, folding and focus for this page.
Data attributeDescription
data-levelThe page's depth, from 0.
data-expandedtrue or false.
data-activePresent on the current page.

FAQ#

`SidebarSwitcher` is one trigger that renders a real `<button>`, so drop it into a `DropdownMenuTrigger asChild` and Radix owns the keyboard, focus, and ARIA. Use it in `SidebarHeader` for the workspace switcher (menu opens down) and in `SidebarFooter` for the profile switcher (menu opens up).