Sidebar
The main navigation of an app: workspace switcher, sections with counts, projects and the user menu, collapsible to icons.
npx prodular-ui add sidebarApp sidebar
Workspace switcher, main nav with counts, a projects group and the user menu. Press cmd+B or drag the rail to collapse.
"use client"
import { AtSignIcon, CheckIcon, ChevronsUpDownIcon, CircleDotIcon, InboxIcon, LogOutIcon, MoreHorizontalIcon, PlusIcon, SearchIcon, SettingsIcon, UserIcon,} from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu"import { Kbd } from "@/components/ui/kbd"import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarTrigger,} from "@/components/ui/sidebar"
const main = [ { label: "Inbox", icon: InboxIcon, count: 24, active: true }, { label: "My issues", icon: CircleDotIcon, count: 7 }, { label: "Mentions", icon: AtSignIcon, count: 2 },]
const projects = [ { label: "Billing migration", color: "bg-chart-1", count: 18 }, { label: "Mobile onboarding", color: "bg-chart-2", count: 9 }, { label: "Q4 pricing page", color: "bg-chart-3", count: 4 }, { label: "SOC 2 renewal", color: "bg-chart-4" },]
const tickets = [ { from: "Dana Reyes", subject: "Invoices show the wrong VAT rate", time: "9:41", unread: true }, { from: "Marcus Lee", subject: "SSO login loops on Safari", time: "9:12", unread: true }, { from: "Elif Kaya", subject: "Can we add a second billing contact?", time: "Yesterday", unread: false }, { from: "Jonas Weber", subject: "Webhook retries stopped after deploy", time: "Yesterday", unread: false }, { from: "Sofia Marin", subject: "Export to CSV drops custom fields", time: "Mon", unread: false },]
export default function SidebarApp() { return ( <SidebarProvider className="relative h-120 min-h-0 overflow-hidden rounded-xl border"> <Sidebar collapsible="icon" className="absolute h-full"> <SidebarHeader> <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}> <span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-sm font-semibold text-primary-foreground"> H </span> <span className="flex min-w-0 flex-1 flex-col leading-tight"> <span className="truncate font-medium">Halcyon</span> <span className="truncate text-xs text-muted-foreground">Business plan, 42 seats</span> </span> <ChevronsUpDownIcon className="ml-auto" /> </DropdownMenuTrigger> <DropdownMenuContent className="w-60"> <DropdownMenuGroup> <DropdownMenuLabel>Workspaces</DropdownMenuLabel> <DropdownMenuItem> Halcyon <CheckIcon className="ml-auto" /> </DropdownMenuItem> <DropdownMenuItem>Northgate Logistics</DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem> <PlusIcon /> Create workspace </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> </SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarGroupContent> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton tooltip="Search"> <SearchIcon /> <span>Search</span> </SidebarMenuButton> <SidebarMenuBadge> <Kbd>⌘K</Kbd> </SidebarMenuBadge> </SidebarMenuItem> {main.map((item) => ( <SidebarMenuItem key={item.label}> <SidebarMenuButton render={<a href="#" />} isActive={item.active} tooltip={item.label}> <item.icon /> <span>{item.label}</span> </SidebarMenuButton> {item.count !== undefined && <SidebarMenuBadge>{item.count}</SidebarMenuBadge>} </SidebarMenuItem> ))} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> <SidebarGroup> <SidebarGroupLabel>Projects</SidebarGroupLabel> <SidebarGroupAction aria-label="New project"> <PlusIcon /> </SidebarGroupAction> <SidebarGroupContent> <SidebarMenu> {projects.map((p) => ( <SidebarMenuItem key={p.label}> <SidebarMenuButton render={<a href="#" />} tooltip={p.label}> <span className="flex size-4 shrink-0 items-center justify-center"> <span className={`size-2 rounded-full ${p.color}`} /> </span> <span>{p.label}</span> </SidebarMenuButton> <SidebarMenuAction showOnHover aria-label={`More options for ${p.label}`}> <MoreHorizontalIcon /> </SidebarMenuAction> </SidebarMenuItem> ))} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </SidebarContent> <SidebarFooter> <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger render={<SidebarMenuButton size="lg" />}> <Avatar> <AvatarFallback>MO</AvatarFallback> </Avatar> <span className="flex min-w-0 flex-1 flex-col leading-tight"> <span className="truncate font-medium">Maya Okafor</span> <span className="truncate text-xs text-muted-foreground">maya@halcyon.dev</span> </span> <ChevronsUpDownIcon className="ml-auto" /> </DropdownMenuTrigger> <DropdownMenuContent side="top" className="w-60"> <DropdownMenuItem> <UserIcon /> Profile </DropdownMenuItem> <DropdownMenuItem> <SettingsIcon /> Preferences <DropdownMenuShortcut>⌘,</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive"> <LogOutIcon /> Log out </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> </SidebarFooter> <SidebarRail /> </Sidebar> <SidebarInset className="min-w-0"> <header className="flex h-12 shrink-0 items-center gap-2 border-b px-3"> <SidebarTrigger /> <span className="text-sm font-medium">Inbox</span> <span className="text-sm text-muted-foreground tabular-nums">24 open</span> </header> <ul className="flex-1 divide-y overflow-y-auto"> {tickets.map((t) => ( <li key={t.subject} className="flex items-center gap-3 px-4 py-3 text-sm"> <span className={t.unread ? "size-1.5 shrink-0 rounded-full bg-primary" : "size-1.5 shrink-0"} /> <span className="w-28 shrink-0 truncate font-medium">{t.from}</span> <span className="min-w-0 flex-1 truncate text-muted-foreground">{t.subject}</span> <span className="shrink-0 text-xs text-muted-foreground tabular-nums">{t.time}</span> </li> ))} </ul> </SidebarInset> </SidebarProvider> )}Icon mode
collapsible icon keeps a 48px rail. Labels move into tooltips.
"use client"
import { AtSignIcon, BarChart3Icon, CircleDotIcon, InboxIcon, LayersIcon, SettingsIcon,} from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger,} from "@/components/ui/sidebar"
const items = [ { label: "Inbox", icon: InboxIcon, count: 24, active: true }, { label: "My issues", icon: CircleDotIcon, count: 7 }, { label: "Mentions", icon: AtSignIcon, count: 2 }, { label: "Views", icon: LayersIcon }, { label: "Reports", icon: BarChart3Icon },]
export default function SidebarIcon() { return ( <SidebarProvider defaultOpen={false} className="relative h-85 min-h-0 overflow-hidden rounded-xl border"> <Sidebar collapsible="icon" className="absolute h-full"> <SidebarHeader> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton size="lg" tooltip="Halcyon"> <span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-sm font-semibold text-primary-foreground"> H </span> <span className="truncate font-medium">Halcyon</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarGroupContent> <SidebarMenu> {items.map((item) => ( <SidebarMenuItem key={item.label}> <SidebarMenuButton render={<a href="#" />} isActive={item.active} tooltip={item.label}> <item.icon /> <span>{item.label}</span> </SidebarMenuButton> {item.count !== undefined && <SidebarMenuBadge>{item.count}</SidebarMenuBadge>} </SidebarMenuItem> ))} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </SidebarContent> <SidebarFooter> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton tooltip="Settings"> <SettingsIcon /> <span>Settings</span> </SidebarMenuButton> </SidebarMenuItem> <SidebarMenuItem> <SidebarMenuButton size="lg" tooltip="Maya Okafor"> <Avatar> <AvatarFallback>MO</AvatarFallback> </Avatar> <span className="truncate font-medium">Maya Okafor</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarFooter> </Sidebar> <SidebarInset className="min-w-0"> <header className="flex h-12 shrink-0 items-center gap-2 border-b px-3"> <SidebarTrigger /> <span className="text-sm text-muted-foreground">Press the toggle or ⌘B to expand</span> </header> <div className="flex flex-1 items-center justify-center p-6 text-sm text-muted-foreground"> Hover an icon to see its label. </div> </SidebarInset> </SidebarProvider> )}Anatomy
<SidebarProvider> <Sidebar collapsible="icon"> <SidebarHeader>{/* workspace switcher */}</SidebarHeader> <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Projects</SidebarGroupLabel> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton render={<a href="/inbox" />} isActive tooltip="Inbox"> <InboxIcon /> <span>Inbox</span> </SidebarMenuButton> <SidebarMenuBadge>24</SidebarMenuBadge> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> </SidebarContent> <SidebarFooter>{/* user menu */}</SidebarFooter> <SidebarRail /> </Sidebar> <SidebarInset>{/* page */}</SidebarInset></SidebarProvider>Guidance
Use it when
- Use as the primary navigation of an app with more than four sections.
- Put the workspace or account switcher in SidebarHeader and the user menu in SidebarFooter.
- Show counts that need attention with SidebarMenuBadge: unread, assigned, failing.
Avoid
- Marketing sites. Use Navigation menu.
- Badges on every item. Only show counts people act on.
- Deep nesting. One level of SidebarMenuSub is the limit.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Wrap the app in SidebarProvider and put Sidebar and SidebarInset inside it, as siblings.
- Use collapsible="icon" for apps people use all day, and give every SidebarMenuButton a tooltip so the icon rail stays usable.
- Mark the current page with isActive on SidebarMenuButton. Only one item is active.
- Render links with render={<a href="..." />} or your router's Link. The default element is a button.
- Every SidebarMenuButton starts with a 16px lucide icon followed by a <span> label, so the label truncates and hides in icon mode.
- Place SidebarMenuBadge and SidebarMenuAction after the button inside the same SidebarMenuItem.
- To show the sidebar inside a framed preview instead of the full viewport, give SidebarProvider a fixed height with className="relative h-120 min-h-0" and Sidebar className="absolute h-full".
API
SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultOpen / open / onOpenChange | boolean | true | Expanded state. Persisted in the sidebar_state cookie. |
Sidebar
| Prop | Type | Default | Description |
|---|---|---|---|
| collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | How it collapses on desktop. |
| variant | "sidebar" | "floating" | "inset" | "sidebar" | floating and inset detach it from the edge. |
| side | "left" | "right" | "left" | Screen edge. |
SidebarMenuButton
| Prop | Type | Default | Description |
|---|---|---|---|
| isActive | boolean | false | Current page. Adds the accent fill and medium weight. |
| size | "sm" | "default" | "lg" | "default" | lg (48px) is for the workspace switcher and the user row. |
| tooltip | string | TooltipContentProps | Shown only when the sidebar is collapsed to icons. |
SidebarMenuAction
| Prop | Type | Default | Description |
|---|---|---|---|
| showOnHover | boolean | false | Hides the action until the item is hovered or focused. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add sidebar# or, with the registry in components.jsonnpx shadcn@latest add @prodular/sidebar