React Sidebar Component Examples
Copy-paste React Sidebar examples: basic layout, collapsible with width transition, grouped sections, item badges, floating variant, and right-side placement.
A sidebar is the right control when a layout has a persistent navigation rail on the side — a dashboard with sections, a settings panel with groups, a docs site with a table of contents that lives outside the main column. The Sidebar component in Drivn is a single-file compound that renders that rail, animates between an expanded 240-pixel width and a collapsed 60-pixel icon-only state, and groups items into collapsible sections through a CSS Grid trick.
Every example on this page imports the component from @/components/ui/sidebar — the path the Drivn CLI writes it under — and reaches into the seven dot-notation members: Sidebar.Header, Sidebar.Content, Sidebar.Footer, Sidebar.Group, Sidebar.Item, Sidebar.Separator, and Sidebar.CollapseButton. Each snippet is TypeScript because Drivn ships TypeScript-only. The Sidebar root is a 'use client' component because the collapsed state lives in React.useState, but you can render it inside a server-component layout — the boundary just starts at the Sidebar itself.
The examples below cover a basic header-and-items layout, a collapsible rail driven by controlled state, grouped sections with chevron headings, items with badges and icons, the floating variant with rounded corners, a right-side placement, and the verbatim component source. The full props table lives on the Sidebar docs page, and the shadcn/ui comparison sits on Drivn vs shadcn Sidebar.
A collapsible rail driven by controlled state
Pass collapsed and onCollapsedChange to drive the rail from a parent. The Sidebar animates between w-[240px] and w-[60px] through a Tailwind transition-[width] duration-200, and every Sidebar.Item reads the same collapsed context to hide its label and badge while keeping the icon. Drop a Sidebar.CollapseButton inside the header to toggle from inside the rail, or wire a button in the main column header to flip the same state from outside.
The example below pairs a controlled Sidebar with a top header that holds an external PanelLeft button. The Sidebar.CollapseButton is the in-rail twin — same icon, same context, same behaviour. Because the state is controlled, you can persist it wherever your app stores user preferences: a localStorage write inside onCollapsedChange, a server action that updates a user profile, or a cookie set by a separate handler. The component does not bake any persistence in.
1 'use client' 2 3 import * as React from "react" 4 import { Sidebar } from "@/components/ui/sidebar" 5 import { Home, Users, Settings, PanelLeft } from "lucide-react" 6 7 export default function Dashboard() { 8 const [collapsed, setCollapsed] = React.useState(false) 9 10 return ( 11 <div className="flex min-h-svh"> 12 <Sidebar 13 collapsed={collapsed} 14 onCollapsedChange={setCollapsed} 15 > 16 <Sidebar.Header> 17 {!collapsed && ( 18 <span className="text-sm font-semibold">Drivn</span> 19 )} 20 <Sidebar.CollapseButton /> 21 </Sidebar.Header> 22 <Sidebar.Content> 23 <Sidebar.Item icon={Home} active>Dashboard</Sidebar.Item> 24 <Sidebar.Item icon={Users}>Team</Sidebar.Item> 25 <Sidebar.Item icon={Settings}>Settings</Sidebar.Item> 26 </Sidebar.Content> 27 </Sidebar> 28 <main className="flex-1"> 29 <header className="flex items-center gap-3 px-4 py-3 border-b"> 30 <button onClick={() => setCollapsed(!collapsed)}> 31 <PanelLeft className="w-4 h-4" /> 32 </button> 33 <h1>Dashboard</h1> 34 </header> 35 <div className="p-6">{/* page content */}</div> 36 </main> 37 </div> 38 ) 39 }
Grouped sections with collapsible headings
Wrap items in Sidebar.Group to add a heading row and group-level collapse. The heading prop draws a small uppercase label, and by default the heading is a <button> that toggles the group open and closed. The chevron icon in the heading rotates 180 degrees when open, and the panel below animates through CSS Grid template rows — grid-template-rows: 1fr when open, 0fr when closed — with overflow-hidden on the inner content so children clip during the transition.
Pass defaultOpen={false} to start a group collapsed, or collapsible={false} to render the heading as a static <span> instead of a toggle. Drop a Sidebar.Separator between groups for an extra visual break when the headings are not enough. When the entire rail is collapsed, group headings hide and panels snap fully open so the collapsed-icon column is never half-clipped.
1 import { Sidebar } from "@/components/ui/sidebar" 2 import { Home, FileText, Settings, Shield } from "lucide-react" 3 4 export default function GroupedSidebar() { 5 return ( 6 <Sidebar> 7 <Sidebar.Content> 8 <Sidebar.Group heading="Main"> 9 <Sidebar.Item icon={Home}>Dashboard</Sidebar.Item> 10 <Sidebar.Item icon={FileText}>Documents</Sidebar.Item> 11 </Sidebar.Group> 12 <Sidebar.Separator /> 13 <Sidebar.Group heading="Settings" defaultOpen={false}> 14 <Sidebar.Item icon={Settings}>General</Sidebar.Item> 15 <Sidebar.Item icon={Shield}>Security</Sidebar.Item> 16 </Sidebar.Group> 17 </Sidebar.Content> 18 </Sidebar> 19 ) 20 }
Items with icons and badges
Every Sidebar.Item accepts an icon prop (a lucide component reference or any element matching the ComponentType<{ className?: string }> shape) and a badge prop (a string or a number). The badge renders on the right with a bg-primary/10 text-primary pill style — that is the verbatim class from the registry — and uses ml-auto to push to the trailing edge of the row. Useful for unread counts, notification dots, or labels like "new".
When the rail is collapsed, the label and the badge hide and only the icon shows. To preserve the badge as a small dot on the icon itself when collapsed, render a custom badge through the children of an Sidebar.Item with absolute-positioned spans — the file is yours after install, so adding a collapsed-state badge slot is a small edit. The default behaviour suits most dashboards: badges visible when expanded, icon-only when collapsed.
1 import { Sidebar } from "@/components/ui/sidebar" 2 import { Home, Inbox, Users, Settings } from "lucide-react" 3 4 export default function InboxSidebar() { 5 return ( 6 <Sidebar> 7 <Sidebar.Content> 8 <Sidebar.Item icon={Home} active> 9 Dashboard 10 </Sidebar.Item> 11 <Sidebar.Item icon={Inbox} badge={12}> 12 Inbox 13 </Sidebar.Item> 14 <Sidebar.Item icon={Users} badge="new"> 15 Team 16 </Sidebar.Item> 17 <Sidebar.Item icon={Settings}> 18 Settings 19 </Sidebar.Item> 20 </Sidebar.Content> 21 </Sidebar> 22 ) 23 }
The floating variant and right-side placement
Pass variant="floating" to render the Sidebar as a detached card with a rounded border, a shadow, and an 8-pixel margin around it (border rounded-xl shadow-lg m-2 h-[calc(100%-16px)]). Useful for marketing app shells where the rail reads as a panel rather than a flush wall. Pair it with a bg-background main column for the strongest contrast.
Pass side="right" to flip the Sidebar to the trailing edge. The component renders the same content; only the border side differs in the default variant. The example below combines both — a floating right-side rail used as a context panel beside a primary content column. The wrapper still uses flex min-h-svh and lets the main column sit on the left with flex-1. For a more substantial right-side panel with stronger separation, drop a Separator between the columns.
1 import { Sidebar } from "@/components/ui/sidebar" 2 import { Home, Filter, BookmarkPlus } from "lucide-react" 3 4 export default function ContextPanel() { 5 return ( 6 <div className="flex min-h-svh"> 7 <main className="flex-1 p-6">{/* primary content */}</main> 8 <Sidebar variant="floating" side="right"> 9 <Sidebar.Header> 10 <span className="text-sm font-semibold">Filters</span> 11 </Sidebar.Header> 12 <Sidebar.Content> 13 <Sidebar.Item icon={Home}>All</Sidebar.Item> 14 <Sidebar.Item icon={Filter} active>Active</Sidebar.Item> 15 <Sidebar.Item icon={BookmarkPlus}>Saved</Sidebar.Item> 16 </Sidebar.Content> 17 </Sidebar> 18 </div> 19 ) 20 }
How the collapse classes are wired
Every example above relies on the same width-transition trick at the root. The styles object below is reproduced verbatim from packages/drivn/src/registry/components/sidebar.ts. The base key holds the flex flex-col h-full column layout plus transition-[width] duration-200 ease-in-out so the animation runs whenever the width class swaps. The variants object selects border-r for the default flush rail and the floating card for the alternative variant.
The width object holds the two width classes — w-[240px] and w-[60px] — that the SidebarRoot function reads through the collapsed state. Whenever you reach for a Sidebar feature that does not have a built-in prop, this styles object is where you change it: bump the expanded width to 280 pixels, swap the rounded corners on floating, or change the border colour to a different token. The file lives in your project after installation, so any edit is a one-line change.
1 // Verbatim from packages/drivn/src/registry/components/sidebar.ts 2 const styles = { 3 base: cn( 4 'flex flex-col h-full bg-card border-border overflow-hidden', 5 'transition-[width] duration-200 ease-in-out' 6 ), 7 variants: { 8 default: 'border-r', 9 floating: cn( 10 'border rounded-xl shadow-lg m-2', 11 'h-[calc(100%-16px)]' 12 ), 13 }, 14 width: { 15 expanded: 'w-[240px]', 16 collapsed: 'w-[60px]', 17 }, 18 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The Sidebar is its own root — collapsed state lives inside SidebarRoot as a single React.useState, with optional controlled overrides via collapsed and onCollapsedChange. You drop it straight into your layout. The internal Sidebar context that Sidebar.Header, Sidebar.Item, and Sidebar.Group read from is created by the root itself and only covers the children inside that Sidebar instance.
The aside uses Tailwind's transition-[width] duration-200 ease-in-out utility, swapping between w-[240px] and w-[60px] when the collapsed state flips. The browser handles the width transition natively, and items inside use the same collapsed context to hide their label and badge with conditional rendering — no per-frame width measurements, no JavaScript animation loop, no resize observer.
Yes, with one caveat — the Sidebar root is a 'use client' component because the collapsed state lives in React.useState. Your layout file can stay a server component; the client boundary starts at the Sidebar itself. The same is true for shadcn/ui's Sidebar setup, which marks SidebarProvider as a client component, so the rendering model is the same.
Wire it up wherever your app already stores user preferences. The Sidebar is controlled-or-uncontrolled, so wrap it in a parent client component that reads from localStorage (or a cookie, or a server-side user profile) on mount, and writes back from onCollapsedChange. The component deliberately ships without persistence baked in because the right store is project-specific.
A CSS Grid trick. Sidebar.Group wraps its children in a div with grid transition-[grid-template-rows] duration-200, and an inline style toggles gridTemplateRows between 1fr (open) and 0fr (closed). The inner content has overflow-hidden so children clip during the transition. This avoids measuring scrollHeight or using max-height with a fixed cap — the panel animates smoothly at any content size.

