Sidebar
A composable application sidebar with cookie-persisted state and a Cmd/Ctrl+B toggle. Three variants, three collapse modes, and a Sheet on mobile.
Structure
Wrap the page in SidebarProvider, render a Sidebar beside a SidebarInset for the main content, and put a SidebarTrigger anywhere inside the provider. This page documents the API with code only – the sidebar takes over the full viewport, so see the showcase for a live demo.
<SidebarProvider>
<Sidebar>
<SidebarHeader />
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<Home />
<span>Dashboard</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter />
<SidebarRail />
</Sidebar>
<SidebarInset>
<header className="flex h-14 items-center border-b px-4">
<SidebarTrigger />
</header>
<div className="p-6">Page content</div>
</SidebarInset>
</SidebarProvider>Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/sidebar.jsonUsage
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar"The provider usually lives in a layout so the sidebar persists across routes. It reads and writes a sidebar_state cookie (7-day max-age), so you can restore the last state on the server:
// app/layout.tsx
import { cookies } from "next/headers"
export default async function Layout({ children }) {
const cookieStore = await cookies()
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
return (
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>
)
}Collapsible modes
collapsible controls what happens when the sidebar closes: offcanvas (default) slides it fully out of view, icon shrinks it to a 3rem icon rail – menu buttons show their tooltip and labels, badges and submenus hide – and none renders a fixed, non-collapsing column.
<Sidebar collapsible="offcanvas" /> // slides out of view (default)
<Sidebar collapsible="icon" /> // collapses to a 3rem icon rail
<Sidebar collapsible="none" /> // always expandedside and variant combine with any mode: floating wraps the content in a padded, rounded, ring-bordered card and inset pairs with SidebarInset to render the main content as a raised, rounded panel.
<Sidebar side="right" variant="floating" collapsible="icon">
...
</Sidebar>API reference
Two components take configuration props; every other part is a styled wrapper that accepts className and the props of the element it renders.
SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultOpen | boolean | true | Initial desktop open state when uncontrolled. |
| open | boolean | – | Controlled open state; pair with onOpenChange. Cookie persistence still runs on change. |
| onOpenChange | (open: boolean) => void | – | Called with the next state on every toggle. |
| style | CSSProperties | – | Merged over the wrapper's CSS variables – override --sidebar-width (16rem) and --sidebar-width-icon (3rem) here to resize the sidebar. |
Sidebar
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "left" | "right" | "left" | Which edge of the viewport the sidebar docks to. |
| variant | "sidebar" | "floating" | "inset" | "sidebar" | Visual treatment: flush full-height column with a border, a floating rounded card, or the inset layout where SidebarInset renders the page as a raised panel. |
| collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | Collapse behavior when closed: slide fully away, shrink to an icon rail, or never collapse. |
SidebarMenuButton
| Prop | Type | Default | Description |
|---|---|---|---|
| isActive | boolean | false | Marks the current item – raised surface background, foreground text and a shadow, reflected as data-active. |
| variant | "default" | "outline" | "default" | Default is transparent with an accent hover; outline adds a background and a hairline shadow ring. |
| size | "default" | "sm" | "lg" | "default" | Row height and text size: h-8, h-7 with text-xs, or h-10 for header-style rows with avatars. |
| tooltip | string | TooltipContent props | – | Tooltip shown to the right of the button, only while the sidebar is icon-collapsed on desktop. |
| render | ReactElement | function | – | Base UI render prop to replace the default button element, e.g. a Link. |
SidebarMenuSubButton
| Prop | Type | Default | Description |
|---|---|---|---|
| isActive | boolean | false | Accent background and text for the current sub-item, reflected as data-active. |
| size | "sm" | "md" | "md" | Text size of the sub-link: text-xs or text-sm (both rows are h-7). |
| render | ReactElement | function | – | Base UI render prop to replace the default anchor element. |
SidebarMenuAction
| Prop | Type | Default | Description |
|---|---|---|---|
| showOnHover | boolean | false | On desktop, keeps the action invisible until the row is hovered or focused (always visible on touch). |
SidebarMenuSkeleton
| Prop | Type | Default | Description |
|---|---|---|---|
| showIcon | boolean | false | Adds a square icon placeholder before the text bar. |
Parts
Every part tags itself with a data-slot attribute (and most with a legacy data-sidebar attribute) for CSS targeting:
| Component | Description |
|---|---|
| SidebarProvider | Context provider and flex wrapper (data-slot="sidebar-wrapper"). Defines --sidebar-width and --sidebar-width-icon and binds the Cmd/Ctrl+B shortcut. |
| Sidebar | The sidebar itself (data-slot="sidebar"). On desktop renders a gap element plus a fixed container (data-slot="sidebar-gap", "sidebar-container", "sidebar-inner"); on mobile renders a Sheet. |
| SidebarTrigger | Ghost icon Button with a PanelLeft icon and sr-only label that calls toggleSidebar (data-slot="sidebar-trigger"). |
| SidebarRail | Invisible grab strip along the sidebar edge that toggles on click, with directional resize cursors (data-slot="sidebar-rail"). |
| SidebarInset | The main element for page content (data-slot="sidebar-inset"). With variant="inset" it becomes a rounded, shadowed panel. |
| SidebarHeader / SidebarFooter | Sticky top and bottom zones of the column (data-slot="sidebar-header" / "sidebar-footer"). |
| SidebarContent | Scrollable middle region with a scroll-driven shadow mask (data-slot="sidebar-content"). |
| SidebarInput | Input restyled for the sidebar surface (data-slot="sidebar-input"). |
| SidebarSeparator | Separator on the sidebar border color with horizontal margins (data-slot="sidebar-separator"). |
| SidebarGroup | Padded section within the content area (data-slot="sidebar-group"). |
| SidebarGroupLabel | Small heading row; fades and slides away in icon mode (data-slot="sidebar-group-label"). Accepts render. |
| SidebarGroupAction | Icon button in the group's top-right corner; hidden in icon mode (data-slot="sidebar-group-action"). Accepts render. |
| SidebarGroupContent | Plain wrapper for the group body (data-slot="sidebar-group-content"). |
| SidebarMenu / SidebarMenuItem | ul and li primitives for a menu list (data-slot="sidebar-menu" / "sidebar-menu-item"). |
| SidebarMenuButton | The main row control (data-slot="sidebar-menu-button"). Reflects data-size and data-active; shrinks to a square icon button in icon mode. |
| SidebarMenuAction | Trailing icon button positioned over the row (data-slot="sidebar-menu-action"), aligned per button size. |
| SidebarMenuBadge | Non-interactive count pinned to the row edge; hidden in icon mode (data-slot="sidebar-menu-badge"). |
| SidebarMenuSkeleton | Loading placeholder row with optional icon and a randomized text width (data-slot="sidebar-menu-skeleton"). |
| SidebarMenuSub / SidebarMenuSubItem | Nested list for child links; hidden in icon mode (data-slot="sidebar-menu-sub" / "sidebar-menu-sub-item"). |
| SidebarMenuSubButton | Indented anchor row for a child link (data-slot="sidebar-menu-sub-button"). Reflects data-size and data-active. |
Behavior & styling hooks
The provider listens for Cmd/Ctrl+B to toggle the sidebar and writes every change to a sidebar_state cookie with a 7-day max-age. Below 768px (useIsMobile) the sidebar renders inside a Sheet with an sr-only title and description, controlled by openMobile instead of open. On desktop the outer element exposes data-state="expanded | collapsed", data-collapsible (set only while collapsed), data-variant and data-side – internal parts style themselves against these via group-data-[collapsible=icon]-style selectors, and you can target them the same way. Widths come from the --sidebar-width and --sidebar-width-icon CSS variables, and colors from the bg-sidebar theme token family.