Sidebar
A composable, collapsible navigation panel for app-level layouts. Built on SidebarProvider context — manages open/closed state, a mobile-responsive Sheet fallback, and a ⌘B keyboard shortcut out of the box.
Anatomy
Wrap the entire layout in SidebarProvider, then place Sidebar and SidebarInset as siblings inside it. SidebarHeader and SidebarFooter are sticky; SidebarContent is the scrollable region.
Navigation Structure
Use SidebarGroup to section content, SidebarMenu / SidebarMenuItem for list structure, and SidebarMenuButton for the interactive element. Compose SidebarMenuBadge and SidebarMenuAction as siblings of the button inside the same SidebarMenuItem.
Loading State
Render SidebarMenuSkeleton while async nav data loads. The showIcon prop adds an icon placeholder. Each skeleton item randomizes its width to reduce perceived layout shift.
Collapsible Modes
The collapsible prop controls how the sidebar collapses. Pair it with SidebarTrigger in the page header — it is also bound to ⌘B / Ctrl+B automatically.
collapsible="offcanvas"Slides fully off-screen. Best default — hides on both mobile and desktop.
collapsible="icon"Collapses to icon-only width. Labels hide; tooltips appear on hover.
collapsible="none"Always visible. Use for fixed layouts where the sidebar should never collapse.
Variants
The variant prop controls the visual treatment of the sidebar panel.
variant="sidebar"Default. Flush with the edge, separated by a border.
variant="floating"Floating panel with a box-shadow ring and rounded corners.
variant="inset"Main content becomes inset with rounded corners and a shadow.
Design Guidelines
Do
- Wrap the root layout. Place
SidebarProviderinapp/layout.tsxso open state persists across navigations. - Use icon mode for dense UIs.
collapsible="icon"withtooltipprops keeps navigation accessible while reclaiming screen space. - Place SidebarTrigger in every page header. Users expect a collapse toggle at the top-left of every page, alongside breadcrumbs.
Don't
- Don't skip SidebarProvider. All sub-components consume sidebar context — rendering any of them outside the provider throws a runtime error.
- Don't mount it inside a route segment. Placing
SidebarProviderin a page component resets state on every navigation. Use the shared layout instead. - Don't hardcode the sidebar width. Adjust via the
--sidebar-widthCSS variable in the provider'sstyleprop — not in class names.
Developer Reference
useSidebar hook
state—"expanded" | "collapsed"open / setOpen— desktop boolean stateopenMobile / setOpenMobile— mobile sheet stateisMobile— true when viewport < 768pxtoggleSidebar()— toggle for current breakpoint
Accessibility
- State exposed via
data-stateanddata-collapsible— target these for CSS transitions. - Mobile renders a
Sheet(role="dialog") with focus trapping and ESC dismissal. SidebarRailprovides a click/drag resize handle; add it as a child ofSidebar.- Keyboard shortcut: ⌘B / Ctrl+B — registered automatically by
SidebarProvider.
Usage
// app/layout.tsx — wrap the root layout
import {
SidebarProvider, Sidebar, SidebarHeader, SidebarContent,
SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarGroupContent,
SidebarGroupAction, SidebarMenu, SidebarMenuItem, SidebarMenuButton,
SidebarMenuBadge, SidebarMenuAction, SidebarInset, SidebarTrigger,
SidebarRail,
} from "@/components/ui/sidebar"
import Link from "next/link"
import { LayoutDashboard, FolderOpen, MoreHorizontal, Plus } from "lucide-react"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" render={<Link href="/" />}>
<AppLogo />
<span>Acme Inc</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarGroupAction title="New project">
<Plus />
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
isActive
tooltip="Dashboard"
render={<Link href="/dashboard" />}
>
<LayoutDashboard />
<span>Dashboard</span>
</SidebarMenuButton>
<SidebarMenuBadge>3</SidebarMenuBadge>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton
tooltip="Projects"
render={<Link href="/projects" />}
>
<FolderOpen />
<span>Projects</span>
</SidebarMenuButton>
<SidebarMenuAction showOnHover title="More options">
<MoreHorizontal />
</SidebarMenuAction>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg">
<UserAvatar />
<div className="flex flex-col text-left leading-none">
<span className="font-semibold">John Doe</span>
<span className="truncate text-xs opacity-70">john@acme.com</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-14 items-center gap-4 border-b px-6">
<SidebarTrigger />
{/* breadcrumbs */}
</header>
{children}
</SidebarInset>
</SidebarProvider>
)
}