Sidebar

Composable dashboard sidebar with expanded, collapsed, and mobile navigation.

Overview

Sidebar provides application-shell structure and behavior: a persistent desktop rail, an icon-collapsed mode, and a mobile navigation surface. You compose the navigation content — links, buttons, badges, headers, and footers.

It intentionally does not own routing, page layouts, or a configuration-only items API. Use SidebarMenuButton asChild with your own anchors or framework links.

Mobile navigation composes the existing Drawer primitive so Sheet can replace or wrap that surface later without rewriting the sidebar API. Install with vinyaas add sidebar or via vinyaas add --catalog dashboard.

Preview

Overview

Toggle the sidebar to switch between expanded and collapsed modes.

Installation

Add the component with the CLI. It copies the files in and installs anything they depend on.

npx vinyaas add sidebar

Usage

import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, } from "@/components/ui/sidebar"; function HomeIcon() { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true" className="size-4 shrink-0" > <path d="M4 10.5 12 4l8 6.5V20a1 1 0 0 1-1 1h-5v-6H10v6H5a1 1 0 0 1-1-1z" /> </svg> ); } export function AppShell() { return ( <SidebarProvider> <Sidebar> <SidebarHeader> <div className="flex items-center gap-2 px-2 py-1.5"> <span className="bg-sidebar-primary text-sidebar-primary-foreground flex size-6 shrink-0 items-center justify-center rounded-md text-[10px] font-semibold"> <span className="truncate text-sm font-medium group-data-[collapsible=icon]:hidden"> Acme </span> A </span> </div> </SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Workspace</SidebarGroupLabel> <SidebarGroupContent> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton isActive tooltip="Dashboard"> <HomeIcon /> <span>Dashboard</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </SidebarContent> </Sidebar> <SidebarInset> <div className="border-border flex items-center gap-2 border-b p-3"> <SidebarTrigger /> <p className="text-sm font-medium">Overview</p> </div> </SidebarInset> </SidebarProvider> ); }

Examples

Dashboard

Grouped navigation, an active item, main content, and an account footer.

Dashboard

Active navigation with account footer

Toggle to collapse

Revenue

Main content stays beside the sidebar.

Active users

Collapse to keep icons and names discoverable.

Collapsed

Icon mode keeps controls usable. Labels stay discoverable via accessible names and tooltips.

Collapsed
Icons remain visible. Labels stay available to assistive tech and tooltips.

Composition

Custom header/footer content and badges. Prefer asChild with anchors for routing.

Custom header, badges, and link composition.

In practice

Start collapsed on dense dashboards: the icon rail stays usable, tooltips name each item, and the inset content stays independent of routing.

Collapsed
Icons remain visible. Labels stay available to assistive tech and tooltips.

API

Other attributes for the underlying element are passed through.

NameTypeDefaultDescription
open / defaultOpen / onOpenChangeboolean | (open: boolean) => voiddefaultOpen = trueSidebarProvider: controlled or uncontrolled expanded state on desktop.
openMobile / defaultOpenMobile / onOpenMobileChangeboolean | (open: boolean) => void—SidebarProvider: controlled or uncontrolled mobile drawer open state.
keyboardShortcutstring | false"b"SidebarProvider: ⌘/Ctrl + key toggles the sidebar. Pass false to disable. Ignored inside inputs.
collapsible"icon" | "offcanvas" | "none""icon"Sidebar: desktop collapse behavior. icon keeps a usable rail; offcanvas hides off-screen.
side"left" | "right""left"Sidebar: which edge hosts the desktop rail and mobile drawer.
asChildbooleanfalseSidebarMenuButton: merge styles onto a child element such as an anchor.
isActivebooleanfalseSidebarMenuButton: marks the current navigation item.
tooltipReactNode—SidebarMenuButton: shown when collapsed on desktop. Also used as aria-label when it is a string.
useSidebar(){ state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar }—Hook for reading and toggling sidebar state from descendants.

Accessibility

Sidebar navigation is a landmark (nav labelled "Sidebar"). Menu actions are native buttons or consumer links. There is no focus trap on the desktop rail.

  • Tab reaches the trigger and menu controls. Enter activates links and buttons; Space activates button controls.
  • Collapsed icon mode keeps items in the tab order. Labels stay in the accessibility tree (sr-only text and/or aria-label from string tooltips). Tooltips are not the only source of naming.
  • Focus-visible rings use --sidebar-ring / --ring tokens.
  • Mobile open state uses Drawer: Escape closes, focus moves into the surface, and focus returns to the trigger on close.
  • Width transitions respect prefers-reduced-motion via motion-reduce:transition-none.
  • Optional keyboard shortcut: ⌘/Ctrl+B (configurable; disable with keyboardShortcut={false}). It does not fire while typing in inputs.