Sidebar

A hand-drawn sidebar that floats inside the page layout, styled with shadcn conventions. It is a set of parts rather than one component, so the shell is yours to compose.

Sidebar
A hand-drawn navigation panel that floats inside the page with an even gap on every side.
Floating panel with collapsible groups
Sketchbook
Sketchbook

Installation

npx shadcn@latest add https://sketchcn.chuwii.com/r/sidebar.json

Usage

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

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarHeader>Sketchcn</SidebarHeader>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupContent>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton isActive>Home</SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <SidebarTrigger />
        {children}
      </SidebarInset>
    </SidebarProvider>
  );
}

Props

Sidebar adds no props of its own. Size it with the variables SidebarProvider sets, overriding them on the provider.

VariableDefaultDescription
--sidebar-width16remWidth when expanded. 18rem inside the mobile sheet.
--sidebar-width-icon3.5remWidth once collapsible="icon" has shrunk it.
--sidebar-gap0.75remPadding around the drawn panel, counted into the collapsed width.

Parts

PartDescription
SidebarProviderOwns the open state and the width variables. Wrap the whole shell in it.
SidebarThe drawn panel, on desktop or inside a sheet on mobile.
SidebarInsetThe main area beside the panel.
SidebarHeaderBlock pinned to the top of the panel.
SidebarContentScrollable middle of the panel.
SidebarFooterBlock pinned to the bottom of the panel.
SidebarRailThin strip along the panel edge that toggles it.
SidebarSeparatorSketched divider sized to the panel padding.
SidebarInputSidebar-sized Input.
SidebarGroupA labelled section of the panel.
SidebarGroupLabelHeading for a group, hidden once collapsed to icons.
SidebarGroupActionControl pinned to the right of the group label.
SidebarGroupContentBody of a group.
SidebarMenuThe ul holding menu items.
SidebarMenuItemA single li.
SidebarMenuButtonThe clickable row, with size and variant scales.
SidebarMenuActionSecondary control pinned to the right of a row.
SidebarMenuBadgeCount or status pinned to the right of a row.
SidebarMenuSkeletonLoading placeholder shaped like a row.
SidebarMenuSubNested ul for child rows.
SidebarMenuSubItemA single nested li.
SidebarMenuSubButtonThe clickable nested row.
SidebarTriggerButton that toggles the panel from anywhere.

Examples

App shell

Studio
Pinned
  • 4
Canvas
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";

export function AppShell({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <Sidebar>
        <SidebarContent>
          <SidebarGroup>
            <SidebarGroupContent>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton isActive>
                    <span>Sketchbook</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarGroupContent>
          </SidebarGroup>
        </SidebarContent>
      </Sidebar>
      <SidebarInset>
        <SidebarTrigger className="m-3" />
        {children}
      </SidebarInset>
    </SidebarProvider>
  );
}

Collapsible groups

Canvas
<SidebarGroup collapsible defaultOpen>
  <SidebarGroupLabel>Workspace</SidebarGroupLabel>
  <SidebarGroupContent>
    <SidebarMenu>
      <SidebarMenuItem>
        <SidebarMenuButton>
          <Star />
          <span>Favourites</span>
        </SidebarMenuButton>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarGroupContent>
</SidebarGroup>

Roomier gap

Canvas
<SidebarProvider
  style={{ "--sidebar-gap": "1.5rem", "--sidebar-width": "14rem" } as React.CSSProperties}
>
  <Sidebar>{/* ... */}</Sidebar>
</SidebarProvider>

Nested pages

Canvas
<SidebarMenuItem collapsible defaultOpen>
  <SidebarMenuButton>
    <Folder />
    <span>Projects</span>
  </SidebarMenuButton>
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton href="#" isActive>
        <span>Hand-drawn icons</span>
      </SidebarMenuSubButton>
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

Collapse to icons

The icon rail is a desktop affordance. On narrow screens the sidebar opens as a sheet instead, so this preview only runs from the md breakpoint up.

<Sidebar collapsible="icon">
  <SidebarContent>
    <SidebarMenu>
      <SidebarMenuItem>
        <SidebarMenuButton isActive tooltip="Overview">
          <Home />
          <span>Overview</span>
        </SidebarMenuButton>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarContent>
</Sidebar>