v1.0

Sidebar

Collapsible application sidebar with a logo-agnostic brand, grouped navigation with a native active state, per-item tooltips while collapsed, and a footer slot.

Pro

Description

Sidebar is a compound app rail: a SidebarBrand header with a collapse SidebarTrigger, grouped navigation (SidebarGroupSidebarGroupLabelSidebarMenuSidebarMenuButton), 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

pnpm dlx @create-ui/cli add sidebar

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.

<Sidebar>
  <SidebarHeader>
    <SidebarBrand />
    <SidebarTrigger />
  </SidebarHeader>
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel />
      <SidebarMenu>
        <SidebarMenuItem>
          <SidebarMenuButton />
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarGroup>
  </SidebarContent>
  <SidebarFooter />
</Sidebar>

Usage

import {
  Sidebar,
  SidebarBrand,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarTrigger,
} from "@/components/ui/sidebar"
<Sidebar>
  <SidebarHeader>
    <SidebarBrand logo={<Logo />} title="Brightpath" subtitle="CRM Dashboard" />
    <SidebarTrigger>
      <RiSidebarFoldLine className="group-data-[state=collapsed]/sidebar:hidden" />
      <RiSidebarUnfoldLine className="hidden group-data-[state=collapsed]/sidebar:block" />
    </SidebarTrigger>
  </SidebarHeader>
 
  <SidebarContent>
    <SidebarGroup>
      <SidebarGroupLabel>Menu</SidebarGroupLabel>
      <SidebarMenu defaultValue="dashboard">
        <SidebarMenuItem>
          <SidebarMenuButton
            value="dashboard"
            tooltip="Dashboard"
            leading={<RiDashboard2Line />}
            trailing={<RiArrowRightSLine />}
            label="Dashboard"
          />
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarGroup>
  </SidebarContent>
 
  <SidebarFooter>{/* user row */}</SidebarFooter>
</Sidebar>

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.

const [collapsed, setCollapsed] = React.useState(false)
 
<Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
  {/* … */}
</Sidebar>

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.

<SidebarMenu defaultValue="dashboard">
  <SidebarMenuItem>
    <SidebarMenuButton
      value="dashboard"
      leading={<RiDashboard2Line />}
      label="Dashboard"
    />
  </SidebarMenuItem>
  <SidebarMenuItem>
    <SidebarMenuButton
      value="contacts"
      leading={<RiUser3Line />}
      label="Contacts"
    />
  </SidebarMenuItem>
</SidebarMenu>

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:

<div className="flex h-dvh">
  <Sidebar>{/* … */}</Sidebar>
  <main className="flex-1 overflow-auto">{/* … */}</main>
</div>

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:

<Sidebar className="rounded-none border-y-0 border-l-0" />
Pro

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:

<div className="relative h-dvh">
  <main className="h-full">{/* … */}</main>
  <div className="absolute inset-y-0 left-0 z-40">
    <Sidebar>{/* … */}</Sidebar>
  </div>
</div>
Pro

Borderless

Drop the border entirely for a rail that reads as one surface with the page:

<Sidebar className="rounded-none border-0" />

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.

KeyDescription
TabMoves focus across the trigger, nav items, and footer.
EnterActivates the focused nav item.
Cmd/Ctrl BToggles the rail between expanded and collapsed.

ARIA notes:

  • SidebarTrigger sets aria-expanded, aria-controls (pointing at the sidebar region), and an aria-label that flips between "Collapse sidebar" and "Expand sidebar".
  • The active SidebarMenuButton gets aria-pressed (it composes TabMenuItem, a toggle button). Rendering via asChild as a <Link> turns it into a link — add aria-current="page" there yourself. Mark at most one item active.
  • While collapsed, pass a tooltip on each item so the icon-only rail is not unlabelled; the collapsed icon is the leading slot.
  • SidebarGroup renders role="group"; pair it with a SidebarGroupLabel for a labelled section.
  • A decorative brand SVG passed as logo should be aria-hidden, or role="img" with an aria-label when it stands in for the brand name.
  • Cmd/Ctrl+B is a global shortcut; it is ignored while focus is in an input, textarea, select, or contenteditable (so it never hijacks native Bold), and keyboardShortcut={false} drops it entirely.

Styling

Tailwind override: pass className to merge Tailwind classes with the component's classes (via cn()) on any part:

<Sidebar className="rounded-none border-y-0 border-l-0" />

Data slots and attributes: the component sets these for CSS targeting.

  • data-slot="sidebar" on the root <aside>; each part sets its own data-slot: sidebar-header, sidebar-brand, sidebar-content, sidebar-group, sidebar-group-label, sidebar-menu, sidebar-menu-item, sidebar-footer. SidebarMenu is a TabMenu, so each item button carries data-slot="tab-menu-item" (there is no sidebar-menu-button slot).
  • data-state="expanded" | "collapsed" on the root and the trigger, reflecting the collapse state. The root also carries the group/sidebar class, so any child can react with group-data-[state=collapsed]/sidebar:….
  • data-active="true" | "false" on the active / inactive SidebarMenuButton (it composes TabMenuItem, whose selected state drives the active look). It also mirrors this to data-state="on" | "off", but while collapsed each button is a Tooltip trigger, so Radix overwrites its data-state — style off data-active.
  • Inside each item (from TabMenuItem): data-slot="tab-menu-item-icon" with data-position="leading" | "trailing" on the icons and data-slot="tab-menu-item-label" on the label.

Collapse your own composites in step with the rail:

[data-slot="sidebar"][data-state="collapsed"] .my-panel {
  /* ... */
}
  • Navbar: use this for the site/app top bar, not the side rail.
  • TabMenu: SidebarMenu is a TabMenu (vertical-button) and each SidebarMenuButton a TabMenuItem; reach for TabMenu on its own to switch between sections inside a page. add sidebar installs it.
  • Tabs: use this when each option swaps a content panel within the same surface.
  • Tooltip: SidebarMenuButton's collapsed tooltip composes it; add sidebar installs 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 — the flex-1 label pushes it right, just before the chevron.

API Reference

The root <aside>. Owns the collapsed state and provides the context that every part reads. Extends React.ComponentProps<"aside">.

Props

PropTypeDefaultDescription
collapsedboolean-Controlled collapsed state. Pair with onCollapsedChange.
defaultCollapsedbooleanfalseUncontrolled initial collapsed state.
onCollapsedChange(collapsed: boolean) => void-Fires with the next state when the rail toggles.
keyboardShortcutbooleantrueWhen true, Cmd/Ctrl+B toggles the rail. Set false to drop it.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Header, content, and footer parts.

SidebarBrand

Brand slot in the header. Logo-agnostic. Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
logoReact.ReactNode-Brand tile contents — your inline <svg> or <img>.
titleReact.ReactNode-Title line. Collapses away with the rail.
subtitleReact.ReactNode-Secondary line under the title. Collapses with the rail.
classNamestring-Tailwind classes merged via cn().

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)

PropTypeDefaultDescription
defaultValuestring-Uncontrolled initial active item value.
valuestring-Controlled active item value. Pair with onValueChange.
onValueChange(value: string) => void-Fires with the next active value when a row is chosen.
size"sm" | "md" | "lg""md"Row size for every item.
classNamestring-Tailwind classes merged via cn().

SidebarMenuButton

A navigation item — a TabMenuItem. Extends React.ComponentProps<typeof TabMenuItem> plus tooltip.

Props

PropTypeDefaultDescription
valuestring-This item's value; active when it matches the menu's value / defaultValue.
selectedboolean-Force this item active, overriding value matching. Use for route-driven active.
labelReact.ReactNode-The row text (a prop, not children).
leadingReact.ReactNode-Icon before the label. Stays and centers when collapsed.
trailingReact.ReactNode-A single icon after the label (chevron). Hidden when collapsed.
childrenReact.ReactNode-Extra content between label and trailing (a count Badge, a status dot).
tooltipReact.ReactNode-Shown in a right-side Tooltip, only while collapsed.
asChildbooleanfalseRender your own element (e.g. a routing <Link>) instead of a <button>.
classNamestring-Tailwind classes merged via cn().

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.