Collapsible application sidebar with a logo-agnostic brand, grouped navigation with a native active state, per-item tooltips while collapsed, and a footer slot.
Description
Sidebar is a compound app rail: a SidebarBrand header with a collapse SidebarTrigger, grouped navigation (SidebarGroup → SidebarGroupLabel → SidebarMenu → SidebarMenuButton), and a SidebarFooter. It collapses between a 300px labelled panel and a 58px icon rail — the width animates, the labels collapse away, and the leading icons glide to center, so nothing jumps. Toggle it with the trigger or Cmd/Ctrl+B.
Reach for it as the persistent left-hand chrome of a dashboard or app. SidebarMenu is a TabMenu (vertical-button) and each SidebarMenuButton a TabMenuItem, so the active row rides on the menu's native value / defaultValue (not a class you bolt on) — the current item gets a soft bg-weak pill, a primary-tinted icon, and a sliding indicator. While collapsed, pass a tooltip and each icon keeps its name.
The search box, invite card, and footer user row in the preview are composition, not parts — they are built from InputGroup, AvatarGroup + Progress, and Avatar. Lift them from the example and swap in your own data.
Sidebar is a Create UI Pro component. With a Pro seat, npx @create-ui/cli add sidebar installs it, and the previews here are marked with a Pro badge. Its collapsed tooltips are built on Tooltip, which add sidebar pulls in automatically.
Installation
Anatomy
Sidebar owns the collapsed state and hands it to its parts through context, so it is a client component. Give it a height — it is h-full, so its parent sets the size.
Usage
The brand ships nothing for you: pass your own logo as SidebarBrand's logo — an inline <svg> (best for a logo: crisp, token-colored, no request) or an <img src>. The row text is the label prop; a count Badge goes in children (not trailing), so the flex-1 label pushes it right, just before the chevron. For a routing link, use <SidebarMenuButton asChild><Link href="…">Label</Link></SidebarMenuButton> and set selected for route-driven active, keeping leading / trailing on SidebarMenuButton.
Examples
Collapsed
Toggle the rail with SidebarTrigger (or Cmd/Ctrl+B). The width animates from 300px to 58px, labels collapse via max-width + opacity, trailing slots hide, and the leading icons glide to center — there is no jump. Drive it yourself with collapsed / defaultCollapsed / onCollapsedChange, or read the state with useSidebar() to make your own composites (search, footer) collapse in step.
Active
SidebarMenu is a TabMenu, so the active row is whichever SidebarMenuButton's value matches the menu's value. Set defaultValue (uncontrolled) or drive value + onValueChange (controlled — set it to your pathname for route-driven nav); or set selected on a single item directly. The active row renders a soft bg-weak pill, tints the leading icon primary-base, and darkens the label to strongest, with a sliding indicator that animates between rows. Mark at most one item active.
Tooltips while collapsed
Pass a tooltip on each SidebarMenuButton. It renders a right-side Tooltip only while the rail is collapsed, so icon-only items keep their name in the 58px state and stay silent when expanded.
Layout
Sidebar is h-full and layout-agnostic. You own the height, the border treatment, and whether it pushes the content or floats over it, so the looks below are all className and composition on your side.
Full height
Give the rail a shell that sets the height. For a full-screen app, use a h-dvh (or h-svh) row. The sidebar sits in flow and pushes the content beside it, which reflows as the rail collapses:
Flush
The rail ships floating by default (bordered and rounded). Dock it flat against a screen edge with className. cn() merges what you pass, so the rounded-none / border-* overrides win, keeping only the divider on the content side:
Overlay
Position the rail absolute over the content so it floats above the page instead of pushing it. Add your own backdrop for the open state:
Borderless
Drop the border entirely for a rail that reads as one surface with the page:
Accessibility
Each SidebarMenuButton is a TabMenuItem button — the menu is a role="group" of aria-pressed toggle buttons (not ARIA tabs, so there is no arrow-key roving; every item is its own tab stop). The active item carries aria-pressed. For real link semantics and aria-current="page", render an <a> / <Link> via asChild and set the attribute yourself.
ARIA notes:
SidebarTriggersetsaria-expanded,aria-controls(pointing at the sidebar region), and anaria-labelthat flips between "Collapse sidebar" and "Expand sidebar".- The active
SidebarMenuButtongetsaria-pressed(it composesTabMenuItem, a toggle button). Rendering viaasChildas a<Link>turns it into a link — addaria-current="page"there yourself. Mark at most one item active. - While collapsed, pass a
tooltipon each item so the icon-only rail is not unlabelled; the collapsed icon is theleadingslot. SidebarGrouprendersrole="group"; pair it with aSidebarGroupLabelfor a labelled section.- A decorative brand SVG passed as
logoshould bearia-hidden, orrole="img"with anaria-labelwhen it stands in for the brand name. Cmd/Ctrl+Bis a global shortcut; it is ignored while focus is in aninput,textarea,select, orcontenteditable(so it never hijacks native Bold), andkeyboardShortcut={false}drops it entirely.
Styling
Tailwind override: pass className to merge Tailwind classes with the component's classes (via cn()) on any part:
Data slots and attributes: the component sets these for CSS targeting.
data-slot="sidebar"on the root<aside>; each part sets its owndata-slot:sidebar-header,sidebar-brand,sidebar-content,sidebar-group,sidebar-group-label,sidebar-menu,sidebar-menu-item,sidebar-footer.SidebarMenuis aTabMenu, so each item button carriesdata-slot="tab-menu-item"(there is nosidebar-menu-buttonslot).data-state="expanded" | "collapsed"on the root and the trigger, reflecting the collapse state. The root also carries thegroup/sidebarclass, so any child can react withgroup-data-[state=collapsed]/sidebar:….data-active="true" | "false"on the active / inactiveSidebarMenuButton(it composesTabMenuItem, whoseselectedstate drives the active look). It also mirrors this todata-state="on" | "off", but while collapsed each button is aTooltiptrigger, so Radix overwrites itsdata-state— style offdata-active.- Inside each item (from
TabMenuItem):data-slot="tab-menu-item-icon"withdata-position="leading" | "trailing"on the icons anddata-slot="tab-menu-item-label"on the label.
Collapse your own composites in step with the rail:
Related Components
- Navbar: use this for the site/app top bar, not the side rail.
- TabMenu:
SidebarMenuis aTabMenu(vertical-button) and eachSidebarMenuButtonaTabMenuItem; reach forTabMenuon its own to switch between sections inside a page.add sidebarinstalls it. - Tabs: use this when each option swaps a content panel within the same surface.
- Tooltip:
SidebarMenuButton's collapsedtooltipcomposes it;add sidebarinstalls it. - Avatar / AvatarGroup: compose the footer user row and the invite card's avatar stack.
- InputGroup: composes the search field in the header.
- Badge: drop one into a
SidebarMenuButton's children for a count — theflex-1label pushes it right, just before the chevron.
API Reference
Sidebar
The root <aside>. Owns the collapsed state and provides the context that every part reads. Extends React.ComponentProps<"aside">.
Props
SidebarBrand
Brand slot in the header. Logo-agnostic. Extends React.ComponentProps<"div">.
Props
SidebarTrigger
The collapse toggle. Reads useSidebar() and flips collapsed on click. Extends React.ComponentProps<"button">.
SidebarContent
The scrollable region between the header and footer. Long menus scroll inside it. Extends React.ComponentProps<"div">.
SidebarGroup / SidebarGroupLabel
SidebarGroup (role="group") wraps a labelled section; SidebarGroupLabel is its overline. Both extend React.ComponentProps<"div">.
SidebarMenu / SidebarMenuItem
SidebarMenu is a TabMenu locked to the vertical-button variant — it owns the active value (value / defaultValue / onValueChange) and the row size. SidebarMenuItem is a transparent (display: contents) wrapper. They extend React.ComponentProps<typeof TabMenu> / React.ComponentProps<"div">.
SidebarMenu props (from TabMenu)
SidebarMenuButton
A navigation item — a TabMenuItem. Extends React.ComponentProps<typeof TabMenuItem> plus tooltip.
Props
SidebarFooter
The bottom slot, below the scroll region. Extends React.ComponentProps<"div">.
useSidebar
Returns the sidebar context: { collapsed, setCollapsed, toggle, id } (id is the sidebar region's id, wired to the trigger's aria-controls). Call it inside a <Sidebar> to build composites (search, footer) that collapse in step; it throws outside one.