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.
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 initStep 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 sidebarThis 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 --allThis 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 resizableThis 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
| Property | Type | Description |
|---|---|---|
| open | boolean | true when sidebar is open / expanded |
| setOpen | (open: boolean) => void | Directly set open state |
| toggleSidebar | () => void | Toggle open/closed |
| isMobile | boolean | true on mobile viewport (Sheet mode) |
| openMobile | boolean | Mobile Sheet open state |
| setOpenMobile | (open: boolean) => void | Control 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.
Related guides
More vibe-coding references
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.