shadcn/ui component guide · 2026

ShipAI.today dev reference

shadcn Sidebar: install, configure, and make it resizable.

The shadcn Sidebar is a composable, accessible sidebar component with built-in collapsible, icon-only, and offcanvas variants. One command installs it. This guide covers adding it to your project, wiring it into a Next.js App Router layout, handling mobile, and enabling drag-to-resize — with exact code.

Installed via npx shadcn@latest add sidebarCollapsible: icon | offcanvas | noneMobile: Sheet-based overlay

Installation

Installing the shadcn Sidebar component

The Sidebar ships as a multi-file component. One CLI command installs all the pieces.

Step 1 — Prerequisites

You need an existing Next.js (or Vite/Remix) project with shadcn/ui and Tailwind CSS already configured. If you haven't initialised shadcn yet:

npx shadcn@latest init

Step 2 — Add the Sidebar

Run the add command. shadcn detects your framework and copies the component files into components/ui/sidebar.tsx.

npx shadcn@latest add sidebar

This installs SidebarProvider, Sidebar, SidebarTrigger, SidebarContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, and all related sub-components. It also installs the Sheet, Separator, and Tooltip dependencies if not already present.

Add all shadcn components at once

If you want to install every available shadcn component in one shot (useful for new boilerplate projects):

npx shadcn@latest add --all

This installs all ~50 components: accordion, alert, avatar, badge, button, calendar, card, carousel, checkbox, collapsible, command, context-menu, dialog, drawer, dropdown-menu, form, hover-card, input, label, menubar, navigation-menu, pagination, popover, progress, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, sonner, switch, table, tabs, textarea, toast, toggle, toggle-group, tooltip, and more.

Basic usage

Wiring the Sidebar into a Next.js App Router layout

The Sidebar needs SidebarProvider at the root. Here's the full layout pattern.

app/layout.tsx — wrap in SidebarProvider

import { SidebarProvider } from "@/components/ui/sidebar";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SidebarProvider>
          {children}
        </SidebarProvider>
      </body>
    </html>
  );
}

components/app-sidebar.tsx — a minimal sidebar

"use client";

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { Home, Settings, Users } from "lucide-react";

const items = [
  { title: "Dashboard", url: "/dashboard", icon: Home },
  { title: "Users",     url: "/users",     icon: Users },
  { title: "Settings",  url: "/settings",  icon: Settings },
];

export function AppSidebar() {
  return (
    <Sidebar>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Application</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map((item) => (
                <SidebarMenuItem key={item.title}>
                  <SidebarMenuButton asChild>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
  );
}

app/dashboard/layout.tsx — place the sidebar + trigger

import { AppSidebar } from "@/components/app-sidebar";
import { SidebarTrigger } from "@/components/ui/sidebar";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-svh">
      <AppSidebar />
      <main className="flex flex-1 flex-col">
        <header className="flex h-14 items-center border-b px-4">
          <SidebarTrigger />
          {/* ... other header content */}
        </header>
        <div className="flex-1 p-6">{children}</div>
      </main>
    </div>
  );
}

SidebarTrigger renders a button that toggles the sidebar open/closed. It reads sidebar state from the nearest SidebarProvider via context — no prop-drilling needed.

Variants

Sidebar collapsible modes

The collapsible prop controls how the sidebar hides. Choose the mode that fits your dashboard UX.

collapsible="icon"

Behavior: Collapses to icon-only strip

Width: Stays on screen, shrinks to ~50px

Best for: Desktop dashboards — users can still see icons while writing notes or working in content area

<Sidebar collapsible="icon">
collapsible="offcanvas"

Behavior: Slides off-screen completely

Width: Hidden entirely (0px) when closed

Best for: Content-first layouts where the full viewport is needed when reading/editing

<Sidebar collapsible="offcanvas">
collapsible="none"

Behavior: Never collapses

Width: Always at full width (default 16rem / 256px)

Best for: Internal admin panels where screen real estate is abundant and navigation is always needed

<Sidebar collapsible="none">

Side prop — left or right

The side prop controls which edge the sidebar appears on. Defaults to "left".

<Sidebar side="right" collapsible="icon">
  {/* ... */}
</Sidebar>

A right-side sidebar is useful for inspector panels, property editors, or secondary navigation (e.g., VS Code's secondary sidebar).

Resizable sidebar

How to make the shadcn Sidebar drag-resizable

Combine shadcn Resizable (ResizablePanelGroup) with the Sidebar for a drag-to-resize layout. The sidebar handles collapse state; Resizable handles drag width.

Step 1 — Install Resizable

npx shadcn@latest add resizable

This adds ResizablePanelGroup, ResizablePanel, and ResizableHandle from react-resizable-panels.

Step 2 — ResizablePanelGroup layout

"use client";

import {
  ResizableHandle,
  ResizablePanel,
  ResizablePanelGroup,
} from "@/components/ui/resizable";
import { AppSidebar } from "@/components/app-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

export default function ResizableLayout({ children }: { children: React.ReactNode }) {
  return (
    <SidebarProvider>
      <ResizablePanelGroup direction="horizontal" className="min-h-svh">
        {/* Sidebar panel — min 12%, max 30%, default 18% */}
        <ResizablePanel defaultSize={18} minSize={12} maxSize={30}>
          <AppSidebar />
        </ResizablePanel>

        {/* Drag handle */}
        <ResizableHandle withHandle />

        {/* Main content panel */}
        <ResizablePanel defaultSize={82}>
          <main className="flex-1 p-6">{children}</main>
        </ResizablePanel>
      </ResizablePanelGroup>
    </SidebarProvider>
  );
}

The withHandle prop on ResizableHandle shows a visible drag grip bar. Omit it for an invisible but still-draggable border.

Persisting sidebar width across navigations

ResizablePanelGroup accepts an autoSaveId prop that persists panel sizes to localStorage automatically:

<ResizablePanelGroup
  direction="horizontal"
  autoSaveId="sidebar-layout"
>
  {/* panels */}
</ResizablePanelGroup>

With autoSaveId set, dragging the sidebar to a new width is remembered between page loads — no server persistence needed.

Mobile

How the shadcn Sidebar handles mobile viewports

SidebarProvider automatically detects small screens and switches the sidebar to a Sheet (drawer) overlay.

What happens on mobile

On viewport widths below the mobile breakpoint (768px by default), SidebarProvider sets isMobile = true via the useIsMobile() hook. The Sidebar component switches from a persistent layout element to a Sheet that slides in from the edge.

This means zero breakpoint CSS needed on your part — the component handles it. The sidebar's open/closed state is still controlled the same way (SidebarTrigger or useSidebar().toggleSidebar()).

Programmatically controlling the sidebar on mobile

"use client";

import { useSidebar } from "@/components/ui/sidebar";

export function MobileMenuButton() {
  const { toggleSidebar, isMobile } = useSidebar();

  if (!isMobile) return null; // only render on mobile

  return (
    <button onClick={toggleSidebar} className="p-2">
      Open menu
    </button>
  );
}

useSidebar hook

Controlling sidebar state programmatically

The useSidebar() hook exposes the full sidebar state and controls. Use it anywhere inside SidebarProvider.

useSidebar() API reference

PropertyTypeDescription
openbooleantrue when sidebar is open / expanded
setOpen(open: boolean) => voidDirectly set open state
toggleSidebar() => voidToggle open/closed
isMobilebooleantrue on mobile viewport (Sheet mode)
openMobilebooleanMobile Sheet open state
setOpenMobile(open: boolean) => voidControl mobile Sheet
state"expanded" | "collapsed"Derived state string for styling

Common patterns

Sidebar patterns for SaaS dashboards

Copy-paste patterns for the most common sidebar UI needs.

Active route highlighting

import { usePathname } from "next/navigation";
import { SidebarMenuButton } from "@/components/ui/sidebar";

// Inside SidebarMenuItem:
const pathname = usePathname();
<SidebarMenuButton
  asChild
  isActive={pathname === item.url}
>
  <Link href={item.url}>
    <item.icon />
    <span>{item.title}</span>
  </Link>
</SidebarMenuButton>

Sidebar header with logo

import { SidebarHeader } from "@/components/ui/sidebar";

<Sidebar>
  <SidebarHeader>
    <div className="flex items-center gap-2 px-4 py-3">
      <Logo className="h-6 w-6" />
      <span className="font-bold">Acme SaaS</span>
    </div>
  </SidebarHeader>
  <SidebarContent>{/* ... */}</SidebarContent>
</Sidebar>

Sidebar footer with user menu

import { SidebarFooter } from "@/components/ui/sidebar";

<Sidebar>
  <SidebarContent>{/* ... */}</SidebarContent>
  <SidebarFooter>
    <SidebarMenu>
      <SidebarMenuItem>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <SidebarMenuButton>
              <Avatar />
              <span>{user.name}</span>
              <ChevronUp className="ml-auto" />
            </SidebarMenuButton>
          </DropdownMenuTrigger>
          {/* dropdown items */}
        </DropdownMenu>
      </SidebarMenuItem>
    </SidebarMenu>
  </SidebarFooter>
</Sidebar>

Nested sub-menu items

import {
  SidebarMenuSub,
  SidebarMenuSubItem,
  SidebarMenuSubButton,
} from "@/components/ui/sidebar";

<SidebarMenuItem>
  <SidebarMenuButton>
    <Settings />
    <span>Settings</span>
  </SidebarMenuButton>
  <SidebarMenuSub>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton asChild>
        <Link href="/settings/profile">Profile</Link>
      </SidebarMenuSubButton>
    </SidebarMenuSubItem>
    <SidebarMenuSubItem>
      <SidebarMenuSubButton asChild>
        <Link href="/settings/billing">Billing</Link>
      </SidebarMenuSubButton>
    </SidebarMenuSubItem>
  </SidebarMenuSub>
</SidebarMenuItem>

Skip the boilerplate

Get a Next.js SaaS with shadcn Sidebar pre-wired

ShipAI.today ships with the shadcn Sidebar already integrated — collapsible icon mode, App Router layout, active route highlighting, user footer, mobile Sheet support, and dark mode. No configuration needed.

Sidebar already installed

shadcn Sidebar + all dependencies are pre-installed. AppSidebar component is wired into the dashboard layout.

All shadcn components included

Every shadcn component is available out of the box — no manual npx add commands needed. Just import and use.

Resizable dashboard layout

The dashboard ships with a ResizablePanelGroup layout. autoSaveId persists sidebar width in localStorage.

FAQ

Frequently asked questions about shadcn Sidebar

How do I add the shadcn Sidebar component?

Run npx shadcn@latest add sidebar. This installs the full Sidebar component family into components/ui/sidebar.tsx. Prerequisites: shadcn/ui init and Tailwind CSS already configured in your project.

How do I add all shadcn components at once?

Run npx shadcn@latest add --all. This installs every available component including accordion, alert, avatar, badge, button, calendar, card, carousel, collapsible, dialog, drawer, dropdown-menu, form, hover-card, input, menubar, navigation-menu, popover, progress, radio-group, resizable, select, separator, sheet, sidebar, skeleton, slider, sonner, switch, table, tabs, textarea, toggle, tooltip, and more.

How do I make the shadcn Sidebar resizable?

Install the Resizable component (npx shadcn@latest add resizable), then wrap your layout in a ResizablePanelGroup. Put the Sidebar inside a ResizablePanel with minSize/maxSize constraints. Add autoSaveId="sidebar-layout" to ResizablePanelGroup to persist the width across sessions.

The sidebar doesn't show on mobile — how do I fix it?

The shadcn Sidebar automatically switches to a Sheet overlay on mobile. Make sure SidebarProvider is at a high level in your layout (root layout.tsx is recommended). Then add a SidebarTrigger button in your header to open/close the Sheet. The trigger can be hidden on desktop with className='md:hidden' if needed.

How do I highlight the active nav item in shadcn Sidebar?

Use the isActive prop on SidebarMenuButton. In Next.js, get the current path with usePathname() from next/navigation, then compare: isActive={pathname === item.url}. This adds the active state styling (background highlight) automatically.

Does the shadcn Sidebar work with Next.js Server Components?

The Sidebar component itself needs to be a Client Component ("use client") because it uses React context (useSidebar hook). However, SidebarProvider can be used in a Server Component layout — just mark it as a client component boundary. Your page content stays a Server Component since it's inside SidebarProvider, not the Sidebar itself.

Ready to ship

Ship a Next.js SaaS with shadcn pre-wired

ShipAI.today includes the full shadcn sidebar, all components installed, resizable dashboard layout, and everything else you need to launch.