Skip to content

Shadcn Sidebar

The shadcn/ui Sidebar is a composable, collapsible application navigation panel, styled with Tailwind CSS v4. A provider manages open and collapsed state, and menu, group, and trigger parts assemble dashboard and admin layouts that adapt to mobile. Sidebars power app shells across ShadcnStore application blocks.

Key features

  • A complete, composable app shell: provider, header, content, footer, menu, rail, inset, and trigger.
  • Three desktop modes: icon collapse, off-canvas hide/show, and a fixed non-collapsible layout.
  • Menu groups, active links, nested links, badges, hover actions, workspace menus, and account menus.
  • Responsive Sheet behavior on mobile, plus ⌘B on macOS / Ctrl+B on Windows.
  • Controlled and uncontrolled state, sidebar-specific width and color tokens, and left/right placement.

Installation

bash
npx shadcn@latest add sidebar

Usage

Wrap the application shell in SidebarProvider. Keep the app sidebar and the page content as siblings, then use SidebarTrigger anywhere inside the provider.

tsx
import {
  SidebarProvider,
  Sidebar,
  SidebarHeader,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarMenu,
  SidebarMenuItem,
  SidebarMenuButton,
  SidebarFooter,
  SidebarRail,
  SidebarInset,
  SidebarTrigger,
} from "@/components/ui/sidebar"

export function Layout() {
  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarHeader>{/* workspace switcher or product identity */}</SidebarHeader>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupContent>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton>Home</SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
        <SidebarFooter>{/* account menu or settings */}</SidebarFooter>
        <SidebarRail />
      </Sidebar>
      <SidebarInset>
        <header>
          <SidebarTrigger />
        </header>
        <main>{/* page content */}</main>
      </SidebarInset>
    </SidebarProvider>
  )
}

Composition

The component is intentionally made of small pieces. Start with this structure, then add only the parts your application needs.

text
SidebarProvider
├── Sidebar
│   ├── SidebarHeader
│   ├── SidebarContent
│   │   └── SidebarGroup
│   │       ├── SidebarGroupLabel
│   │       ├── SidebarGroupAction
│   │       └── SidebarMenu
│   │           └── SidebarMenuItem
│   │               ├── SidebarMenuButton
│   │               ├── SidebarMenuAction
│   │               ├── SidebarMenuBadge
│   │               └── SidebarMenuSub
│   ├── SidebarFooter
│   └── SidebarRail
├── SidebarInset
└── SidebarTrigger

Collapsible modes

Use collapsible="icon" when users should retain quick access to navigation icons. Use offcanvas when the sidebar should disappear completely. Use none only for fixed layouts that are never meant to collapse.

tsx
<Sidebar collapsible="icon">...</Sidebar>
<Sidebar collapsible="offcanvas">...</Sidebar>
<Sidebar collapsible="none">...</Sidebar>

The app-shell demo above uses icon collapse. Its trigger, rail, workspace menu, account menu, active item, badge, hover action, and nested navigation are all interactive.

Variants and placement

sidebar is the standard edge-to-edge layout. floating adds an inset, rounded sidebar panel. inset also expects the main content to be wrapped with SidebarInset.

tsx
<Sidebar variant="sidebar" />
<Sidebar variant="floating" />

<SidebarProvider>
  <Sidebar variant="inset" />
  <SidebarInset>{/* page content */}</SidebarInset>
</SidebarProvider>

<Sidebar side="right" />

Use a real link for navigation, set isActive on the current destination, and use a nested menu when the hierarchy is already visible in the page structure.

tsx
<SidebarMenuItem>
  <SidebarMenuButton render={<a href="/projects" />} isActive>
    <FolderKanban />
    <span>Projects</span>
  </SidebarMenuButton>
  <SidebarMenuBadge>12</SidebarMenuBadge>
  <SidebarMenuAction showOnHover aria-label="Project actions">
    <MoreHorizontal />
  </SidebarMenuAction>
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton render={<a href="/projects/recent" />}>
        Recent
      </SidebarMenuSubButton>
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

For compact app shells, SidebarMenuButton accepts tooltip. The tooltip appears only when an icon sidebar is collapsed.

Provider state and keyboard shortcut

SidebarProvider manages uncontrolled state by default and writes the desktop preference to a cookie when it changes. Pass open and onOpenChange to make it controlled; read the cookie at your application boundary when you want to restore a preference after reload.

tsx
function AppShell() {
  const [open, setOpen] = React.useState(true)

  return (
    <SidebarProvider open={open} onOpenChange={setOpen}>
      <Sidebar />
      <SidebarInset>
        <SidebarTrigger />
      </SidebarInset>
    </SidebarProvider>
  )
}

Users can toggle the sidebar with ⌘B on macOS or Ctrl+B on Windows. useSidebar() exposes state, open, setOpen, openMobile, setOpenMobile, isMobile, and toggleSidebar for custom controls.

Responsive behavior

Below the desktop breakpoint, Sidebar renders as a slide-in Sheet. Keep a visible SidebarTrigger in the page header and ensure each menu destination can receive focus. The same provider automatically switches the trigger between desktop collapse and mobile Sheet state.

Props

ComponentPropValuesDefault
SidebarProviderdefaultOpenbooleantrue
SidebarProvideropen / onOpenChangecontrolled boolean state
Sidebarsideleft / rightleft
Sidebarvariantsidebar / floating / insetsidebar
Sidebarcollapsibleoffcanvas / icon / noneoffcanvas
SidebarMenuButtonisActivebooleanfalse
SidebarMenuButtontooltiplabel or tooltip props

Theming and width

Sidebar uses dedicated semantic tokens, so its surface can differ from the main application without hard-coded colors. Adjust a single sidebar’s width through provider style variables.

tsx
<SidebarProvider
  style={
    {
      "--sidebar-width": "18rem",
      "--sidebar-width-mobile": "20rem",
    } as React.CSSProperties
  }
>
  <Sidebar />
</SidebarProvider>

The sidebar color tokens are --sidebar, --sidebar-foreground, --sidebar-primary, --sidebar-accent, --sidebar-border, and --sidebar-ring.

Built on shadcn primitives

The Sidebar is composed from primitives shadcn already installs; its collapsing behavior is provided by SidebarProvider.

Accessibility

  • Keep SidebarTrigger reachable and visible in the application header.
  • Use anchors for destinations and buttons only for actions such as opening a menu or toggling a collapsible group.
  • Mark the current destination with isActive; in routed applications also provide the appropriate current-page semantics.
  • On mobile, the Sidebar becomes a dialog-style Sheet with focus handling. Test the open, close, and Escape paths on the final application.
  • Give icon-only actions an accessible label and retain tooltips as supplementary—not sole—labels.

When to use the Sidebar

Use a Sidebar for primary navigation in dashboards, admin panels, and apps that need persistent, collapsible navigation. For a temporary side panel, use a Sheet; for adjustable split layouts, use Resizable panels.

Used in these blocks

The Sidebar uses Button-style menu items, a Separator between groups, and often a Dropdown Menu for the account menu. On mobile it behaves like a Sheet.

FAQ

What is the shadcn sidebar used for?

The Sidebar is a composable, collapsible application navigation panel with a provider, menu, groups, and a trigger, used to build dashboard and admin layouts.

How does the sidebar collapse?

SidebarProvider manages open state; SidebarTrigger toggles it, and the collapsible prop controls whether it collapses to icons, offcanvas, or stays fixed.

Does the sidebar work on mobile?

Yes. On small screens it renders as a slide-in sheet, and the trigger opens it.

How do I persist the collapsed state?

The provider writes the state to a sidebar_state cookie. Read that cookie at your application boundary and pass the result as defaultOpen, or manage open yourself with onOpenChange.