An app sidebar that collapses to icons, and becomes a sheet on mobile.

Dashboard

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.json

Needs 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

Docs
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

NameTypeDefaultDescription
SidebarProvider defaultOpen / open / onOpenChangeboolean / boolean / (open: boolean) => voidtrueUncontrolled or controlled expanded state on desktop.
SidebarProvider storageKeystring | false'sidebar_state'Name of the cookie that remembers the collapsed state. false turns it off. Storage failures are ignored.
SidebarProvider shortcutstring | 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 / asChildboolean / ReactNode / boolean—Current page, label shown while collapsed, render as a link. Keep the label in a plain <span>.
SidebarMenuBadge, SidebarMenuSub, SidebarMenuSubButtoncomponent—A count and nested links; both hide while collapsed.
SidebarTriggerButton 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-icon16rem, 3remSet them on SidebarProvider’s style to resize.