Sidebar
A composable, themeable sidebar with collapsible, mobile, and icon-only modes.
"use client";
import * as React from "react";
import {
BoxIcon,
CalendarIcon,
HomeIcon,
InboxIcon,
SearchIcon,
SettingsIcon,
} from "lucide-react";
import { Separator } from "@/registry/base/ui/separator";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/registry/base/ui/sidebar";
const items = [
{ title: "Home", icon: HomeIcon, isActive: true },
{ title: "Inbox", icon: InboxIcon, badge: "24" },
{ title: "Calendar", icon: CalendarIcon },
{ title: "Search", icon: SearchIcon },
{ title: "Settings", icon: SettingsIcon },
];
export default function SidebarDemo() {
return (
<SidebarProvider className="min-h-0 h-[560px]">
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" render={<a href="#" />}>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<BoxIcon className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">Acme Inc</span>
<span className="truncate text-xs text-sidebar-foreground/70">
Enterprise
</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={item.isActive}
render={<a href="#" />}
>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
{item.badge && (
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton render={<a href="#" />}>
<SettingsIcon />
<span>Settings</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger />
<Separator orientation="vertical" className="h-4 my-auto" />
<span className="text-sm font-medium">Home</span>
</header>
<div className="flex flex-1 flex-col gap-4 p-4">
<div className="grid auto-rows-min grid-cols-3 gap-4">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
<div className="flex-1 rounded-xl bg-muted/50" />
</div>
</SidebarInset>
</SidebarProvider>
);
}
Installation
npx shadcn@latest add https://sevenui.dev/r/sidebar.jsonThis is a multi-file item: it installs components/ui/sidebar.tsx alongside
hooks/use-mobile.ts, the breakpoint hook the sidebar uses to switch into
its mobile sheet.
Usage
"use client";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
export default function Layout() {
return (
<SidebarProvider>
<Sidebar collapsible="icon">
<SidebarHeader>{/* app switcher */}</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Application</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Home" render={<a href="#" />}>
<span>Home</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>{/* user menu */}</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 items-center gap-2 border-b px-4">
<SidebarTrigger />
</header>
{/* page content */}
</SidebarInset>
</SidebarProvider>
);
}
API reference
There is no Base UI sidebar primitive — the component is built from our own
Sheet, Tooltip, and
plain elements, the same shape as shadcn/ui’s sidebar. It composes with
Collapsible if you nest collapsible groups
inside SidebarGroup yourself.
Provider and context
SidebarProviderwraps the whole layout (sidebar + page content) and owns the open/closed state. It renders aTooltipProvider delay={0}internally so everySidebarMenuButtontooltip shares one hover-delay group, plus a wrapper<div>carrying the--sidebar-width/--sidebar-width-iconCSS variables.open/defaultOpen(defaulttrue) /onOpenChangecontrol the desktop expanded/collapsed state, same shape as a controlled/uncontrolled Base UI root.useSidebar()reads the context —state("expanded" | "collapsed"),open,setOpen,openMobile,setOpenMobile,isMobile, andtoggleSidebar(). It throws if called outside aSidebarProvider.- Keyboard shortcut:
⌘/Ctrl BcallstoggleSidebar()from anywhere on the page. - Cookie persistence: every
setOpencall writes asidebar_statecookie (max-ageone week) so the desktop expanded/collapsed state survives a reload. Read that cookie server-side and pass it asSidebarProvider’sdefaultOpento avoid a flash of the wrong state on first paint. - Mobile behavior: below the 768px breakpoint (
useIsMobile, the installed hook),Sidebarrenders inside our Sheet instead of the fixed desktop layout —openMobile/setOpenMobilecontrol it, andSidebarTrigger/toggleSidebar()target whichever mode is active.
Sidebar
| Prop | Type | Default |
|---|---|---|
side |
"left" | "right" |
"left" |
variant |
"sidebar" | "floating" | "inset" |
"sidebar" |
collapsible |
"offcanvas" | "icon" | "none" |
"offcanvas" |
Pair variant="inset" with SidebarInset as the main content wrapper to get
the card-like inset layout. collapsible="none" renders a plain, always-open
sidebar with no rail, no offcanvas animation, and no mobile sheet.
SidebarMenuButton
| Prop | Type | Default |
|---|---|---|
isActive |
boolean |
false |
variant |
"default" | "outline" |
"default" |
size |
"default" | "sm" | "lg" |
"default" |
tooltip |
React.ReactNode |
— |
render |
useRender.ComponentProps<"button">["render"] |
— |
Passing tooltip (a string or TooltipContent props) wraps the button
in Tooltip / TooltipTrigger / TooltipContent automatically; the
tooltip is forced hidden unless the sidebar is in its icon-only
collapsed state on desktop, so it only appears once labels are hidden.
Other parts
SidebarTrigger, SidebarRail, SidebarInset, SidebarInput,
SidebarHeader, SidebarFooter, SidebarSeparator, SidebarContent,
SidebarGroup, SidebarGroupLabel, SidebarGroupAction,
SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuAction,
SidebarMenuBadge, SidebarMenuSkeleton, SidebarMenuSub,
SidebarMenuSubItem, and SidebarMenuSubButton round out the surface —
mirroring shadcn/ui’s sidebar one-for-one, each a thin styled wrapper around
a <div>/<button>/<ul>/<li>/<a>.
Divergences from shadcn/ui
renderinstead ofasChild.SidebarGroupLabel,SidebarGroupAction,SidebarMenuButton,SidebarMenuAction, andSidebarMenuSubButtonaccept arenderprop (viauseRenderfrom@base-ui/react/use-render) instead of shadcn’sasChildboolean — passrender={<a href="/home" />}rather than wrapping children inasChild.data-activeis a presence attribute, styled withdata-[active]:— notdata-[active=true]like shadcn/ui. PassingisActive={false}(or omitting it) omits the attribute entirely rather than setting it to"false".- Mobile detection uses our own
useIsMobilehook (installed alongside the component as aregistry:hookfile), matching shadcn/ui’s hook one-for-one. - Tooltip wiring uses our Tooltip’s Base UI-derived props —
TooltipProvider delay={0}(notdelayDuration={0}) — since Tooltip here wraps Base UI’s Tooltip primitive rather than Radix. data-sidebar,data-state,data-collapsible,data-variant,data-side, anddata-sizeare sidebar’s own attributes, set by its own code on plain elements — not Base UI state attributes — so the value-carryinggroup-data-[collapsible=icon]:-style selectors used throughout are intentional here, unlike other SevenUI components built directly on Base UI primitives.
Theming
Sidebar reads a dedicated set of --sidebar-* tokens, installed with the
theme registry item: --sidebar, --sidebar-foreground,
--sidebar-primary, --sidebar-primary-foreground, --sidebar-accent,
--sidebar-accent-foreground, --sidebar-border, and --sidebar-ring.
Override them to retheme the sidebar independently of the rest of the page.