Sidebar
An app sidebar that collapses to icons, and becomes a sheet on mobile.
Press ⌘B or Ctrl+B to toggle.
'use client'
import * as React from 'react'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarProvider,
SidebarTrigger,
} from '@/components/ui/sidebar'
const platform = [
{ title: 'Dashboard', icon: HomeIcon },
{ title: 'Projects', icon: FolderIcon, badge: '12' },
{ title: 'Inbox', icon: InboxIcon, badge: '3' },
{ title: 'Settings', icon: SettingsIcon },
]
/**
* Collapses to an icon rail with the trigger or ⌘/Ctrl + B; the state is remembered in a cookie. On a phone
* it opens as an off-canvas sheet. The frame below only keeps this preview inside the docs page.
*/
export default function SidebarDemo() {
const [active, setActive] = React.useState('Dashboard')
return (
<div className="relative h-[440px] w-full overflow-hidden rounded-lg border border-border bg-background">
<SidebarProvider className="h-full min-h-0" storageKey="kagadmodyaa_sidebar_demo">
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip="Kagadmodyaa">
<span data-slot="mark" className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-semibold text-primary-foreground">
K
</span>
<span className="grid text-left leading-tight">
<span className="truncate text-sm font-medium">Kagadmodyaa</span>
<span className="truncate text-xs text-muted-foreground">Studio plan</span>
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
{platform.map(item => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton tooltip={item.title} isActive={active === item.title} onClick={() => setActive(item.title)}>
<item.icon />
<span>{item.title}</span>
</SidebarMenuButton>
{item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
{item.title === 'Projects' && (
<SidebarMenuSub>
<li>
<SidebarMenuSubButton href="#website">Website</SidebarMenuSubButton>
</li>
<li>
<SidebarMenuSubButton href="#mobile">Mobile app</SidebarMenuSubButton>
</li>
</SidebarMenuSub>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Ada Lovelace">
<span data-slot="mark" className="flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-medium">AL</span>
<span className="truncate">Ada Lovelace</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header className="flex h-12 shrink-0 items-center gap-2 border-b border-border px-3">
<SidebarTrigger />
<span className="text-sm font-medium">{active}</span>
</header>
<div className="grid flex-1 content-start gap-3 overflow-auto p-4">
<div className="h-20 rounded-lg bg-muted/60" />
<div className="grid grid-cols-2 gap-3">
<div className="h-24 rounded-lg bg-muted/60" />
<div className="h-24 rounded-lg bg-muted/60" />
</div>
<p className="text-xs text-muted-foreground">Press ⌘B or Ctrl+B to toggle.</p>
</div>
</SidebarInset>
</SidebarProvider>
</div>
)
}
function Icon({ children }: { children: React.ReactNode }) {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round">
{children}
</svg>
)
}
function HomeIcon() {
return (
<Icon>
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<path d="M9 22V12h6v10" />
</Icon>
)
}
function FolderIcon() {
return (
<Icon>
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
</Icon>
)
}
function InboxIcon() {
return (
<Icon>
<path d="M22 12h-6l-2 3h-4l-2-3H2" />
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" />
</Icon>
)
}
function SettingsIcon() {
return (
<Icon>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h0a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</Icon>
)
}
Built on Base UI
Installation
pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/sidebar.jsonNeeds a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.
Sidebar imports cn() from lib/utils.ts — add it too if you don't have it yet:
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Usage
import {
SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarFooter,
SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuItem, SidebarMenuButton,
SidebarInset, SidebarTrigger,
} from '@/components/ui/sidebar'
// app/layout.tsx — the provider wraps the sidebar and the page
<SidebarProvider>
<Sidebar collapsible="icon" className="sticky top-0 h-svh self-start">
<SidebarHeader>…</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Platform</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="Dashboard" isActive>
<HomeIcon />
<span>Dashboard</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>…</SidebarFooter>
</Sidebar>
<SidebarInset>
<SidebarTrigger />
{children}
</SidebarInset>
</SidebarProvider>Examples
Off-canvas, on the right
Use the button above to slide the sidebar in from the right.
'use client'
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarTrigger,
} from '@/components/ui/sidebar'
const links = ['Getting started', 'Components', 'Changelog']
/** `collapsible="offcanvas"` hides the whole sidebar instead of shrinking it; this one is on the right and starts closed. */
export default function SidebarOffcanvas() {
return (
<div className="relative h-[260px] w-full overflow-hidden rounded-lg border border-border bg-background">
<SidebarProvider className="h-full min-h-0" defaultOpen={false} storageKey={false} shortcut={false}>
<SidebarInset>
<header className="flex h-12 shrink-0 items-center justify-between border-b border-border px-3">
<span className="text-sm font-medium">Docs</span>
<SidebarTrigger />
</header>
<div className="p-4 text-sm text-muted-foreground">Use the button above to slide the sidebar in from the right.</div>
</SidebarInset>
<Sidebar side="right" collapsible="offcanvas">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>On this site</SidebarGroupLabel>
<SidebarMenu>
{links.map(link => (
<SidebarMenuItem key={link}>
<SidebarMenuButton>
<span>{link}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</SidebarProvider>
</div>
)
}
API Reference
| Name | Type | Default | Description |
|---|---|---|---|
| SidebarProvider defaultOpen / open / onOpenChange | boolean / boolean / (open: boolean) => void | true | Uncontrolled or controlled expanded state on desktop. |
| SidebarProvider storageKey | string | false | 'sidebar_state' | Name of the cookie that remembers the collapsed state. false turns it off. Storage failures are ignored. |
| SidebarProvider shortcut | string | false | 'b' | Toggles with Cmd/Ctrl + this key. false disables it. |
| Sidebar collapsible | 'icon' | 'offcanvas' | 'none' | 'icon' | Shrink to an icon rail, slide away entirely, or never collapse. |
| Sidebar side | 'left' | 'right' | 'left' | Which edge it sits on. Below 768px it becomes an off-canvas Sheet. |
| SidebarMenuButton isActive / tooltip / asChild | boolean / ReactNode / boolean | — | Current page, label shown while collapsed, render as a link. Keep the label in a plain <span>. |
| SidebarMenuBadge, SidebarMenuSub, SidebarMenuSubButton | component | — | A count and nested links; both hide while collapsed. |
| SidebarTrigger | Button props | — | Toggles the sidebar, or opens the sheet on mobile. |
| useSidebar() | () => SidebarContextValue | — | state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar. |
| CSS variables | --sidebar-width, --sidebar-width-icon | 16rem, 3rem | Set them on SidebarProvider’s style to resize. |