Skip to content
Drivn
6 min read

How to Add a Sidebar to a React App

Step-by-step guide to adding a collapsible sidebar to any React app with the Drivn CLI — a copy-paste, dot-notation nav with icons and groups, zero deps.

Reaching for a sidebar means your React app has grown a left rail of navigation — a dashboard, an admin panel, a settings screen where the primary links live down the side rather than across the top. You could nest a pile of <nav>, <ul>, and <button> markup and hand-wire the collapse toggle yourself, but you end up re-solving the same problems: the expand/collapse width transition, the active-item highlight, collapsible groups, and the icon-only mode when the rail is narrow.

Drivn solves it by copy-paste: the CLI writes a sidebar.tsx file into your repository. The Sidebar is a compound component with dot-notation parts — Sidebar.Header, Sidebar.Content, Sidebar.Group, Sidebar.Item, Sidebar.Footer, Sidebar.Separator, and Sidebar.CollapseButton — that you compose to shape the rail. It tracks its collapsed state in React context, animates the width between w-[240px] and w-[60px], and switches items to icon-only when collapsed, so the whole open-and-closed behavior is built in.

Because it holds state and shares it through context, sidebar.tsx opens with a 'use client' directive and renders inside a Client Component. This guide adds the Sidebar to any React app — install the CLI, compose your first rail, wire the collapse toggle, and restyle it from the file you own. For the App Router walkthrough see the Next.js Sidebar guide, and for copy-paste layouts see the Sidebar examples.

Prerequisites

Before installing Sidebar, confirm your React project has the two things Drivn assumes: Tailwind CSS v4 installed and processing your styles, and a @/ path alias pointing at your source directory (the component ships as a .tsx file, so TypeScript is part of the stack). If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, both are already wired; for a custom setup the installation page lists the minimal config. The Sidebar also uses lucide-react for its two built-in icons — the ChevronDown on collapsible group headings and the PanelLeft on the collapse button — so make sure that package is installed; it is the same icon set Drivn uses across the library. The one detail worth knowing before you compose it is that the Sidebar is a Client Component: it keeps its collapsed state in React context, so it renders inside a client boundary rather than straight in a Server Component.

Step 1 — Install Drivn via the CLI

Run the CLI from your project root to add the Sidebar source file. The command prompts once for your install directory (defaulting to src/components/ui/) and writes a single sidebar.tsx that holds the root component and all seven compound parts. No global config file is created; the result is TypeScript source you edit like any other component. Confirm it landed in your editor, then commit it for a clean baseline. If your project uses a monorepo layout or a non-standard source path, the CLI docs cover the flags for targeting a custom location during install. Because the file is yours after install, future Drivn releases will not overwrite your edits — rename a part, change the collapsed width, or drop the floating variant, and the changes stick.

1# add the Sidebar source file
2npx drivn add sidebar

Step 2 — Compose your first sidebar

Import Sidebar and compose the rail from its dot-notation parts. Sidebar.Header holds your branding, Sidebar.Content is the scrollable middle, Sidebar.Group labels a set of links, and each Sidebar.Item is a nav row that takes an icon (a lucide component reference like Home, not a rendered element), an optional active flag for the current page, and an optional badge for a count. The example below is a minimal dashboard rail — a title, one group, and three items with icons — the same shape the Sidebar docs preview uses. Pass the icon as a component reference (icon={Home}) and the component renders it for you at the right size; the active item picks up the accent background so the current route reads at a glance.

1'use client'
2
3import { Sidebar } from '@/components/ui/sidebar'
4import { Home, Inbox, Users } from 'lucide-react'
5
6export function AppSidebar() {
7 return (
8 <Sidebar>
9 <Sidebar.Header>
10 <span className="text-sm font-semibold">Drivn</span>
11 </Sidebar.Header>
12 <Sidebar.Content>
13 <Sidebar.Group heading="Menu">
14 <Sidebar.Item icon={Home} active>Dashboard</Sidebar.Item>
15 <Sidebar.Item icon={Inbox} badge={3}>Inbox</Sidebar.Item>
16 <Sidebar.Item icon={Users}>Team</Sidebar.Item>
17 </Sidebar.Group>
18 </Sidebar.Content>
19 </Sidebar>
20 )
21}

Step 3 — Wire the collapse toggle

The Sidebar collapses to an icon-only rail, and you drive that either from inside or outside the component. The built-in Sidebar.CollapseButton toggles it with no wiring — drop it in the header and it flips the state through context, animating the width from w-[240px] down to w-[60px] and hiding every item label. To control it from elsewhere — a toggle in your top bar, a keyboard shortcut — pass collapsed and onCollapsedChange and own the boolean in state; the component reads your value instead of its own. When collapsed, each Sidebar.Item drops its label and centers its icon, and group headings hide, so the rail stays usable at 60 pixels wide. The example below owns the state so an outside button and the built-in toggle can both drive it. If you only need a static starting state, pass defaultCollapsed and skip the state entirely.

1'use client'
2
3import { useState } from 'react'
4import { Sidebar } from '@/components/ui/sidebar'
5import { Home, Users, Settings } from 'lucide-react'
6
7export function CollapsibleSidebar() {
8 const [collapsed, setCollapsed] = useState(false)
9
10 return (
11 <Sidebar collapsed={collapsed} onCollapsedChange={setCollapsed}>
12 <Sidebar.Header>
13 {!collapsed && <span className="text-sm font-semibold">Drivn</span>}
14 <Sidebar.CollapseButton />
15 </Sidebar.Header>
16 <Sidebar.Content>
17 <Sidebar.Item icon={Home} active>Dashboard</Sidebar.Item>
18 <Sidebar.Item icon={Users}>Team</Sidebar.Item>
19 <Sidebar.Item icon={Settings}>Settings</Sidebar.Item>
20 </Sidebar.Content>
21 </Sidebar>
22 )
23}

Step 4 — Group, restyle, and compose

Two more parts shape a real rail. Sidebar.Group takes a heading and, by default, makes it collapsible — clicking the heading toggles the group's items open and closed with a grid-template-rows animation, and defaultOpen={false} starts it closed. Sidebar.Separator draws a rule between groups. Set variant="floating" on the root for a rounded, shadowed rail that sits inset from the edge, or side="right" to anchor it on the opposite side. Everything visible is a Tailwind class in the styles object inside the file you own, so restyling is a local edit: change width.collapsed to make the rail wider when closed, swap the itemActive background for a branded accent, or adjust the header border. Because those classes read your theme tokens, the rail matches dark and light mode with no override. For copy-paste layouts covering groups, badges, and the floating variant see the Sidebar examples.

1<Sidebar>
2 <Sidebar.Content>
3 <Sidebar.Group heading="Main">
4 <Sidebar.Item icon={Home}>Dashboard</Sidebar.Item>
5 <Sidebar.Item icon={FileText}>Documents</Sidebar.Item>
6 </Sidebar.Group>
7 <Sidebar.Separator />
8 <Sidebar.Group heading="Settings" defaultOpen={false}>
9 <Sidebar.Item icon={Settings}>General</Sidebar.Item>
10 <Sidebar.Item icon={Shield}>Security</Sidebar.Item>
11 </Sidebar.Group>
12 </Sidebar.Content>
13</Sidebar>
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

Follow Drivn updates
New components, improvements, and guides every release.
Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Yes. The Sidebar keeps its collapsed state and shares it with the compound parts through React context, and its groups track their own open state — so its source opens with a 'use client' directive and renders inside a Client Component. In the Next.js App Router, put the Sidebar in a file that starts with 'use client' (or import one that does). In a Vite or Create React App project the directive is simply ignored and the component works the same.

Pass active to the Sidebar.Item for the current route. It picks up the accent background and foreground color so the current page reads at a glance, while the other items stay muted until hovered. In a Next.js app, compute which item is active from the pathname with usePathname() from next/navigation and pass active={pathname === item.href}; in a Vite app, track it in your own router state and pass the same boolean.

The Sidebar animates its width between w-[240px] expanded and w-[60px] collapsed, hiding item labels and group headings so only the icons remain. Toggle it with the built-in Sidebar.CollapseButton, or control it from outside by passing collapsed and onCollapsedChange and owning the boolean in state. For a fixed starting state with no toggle, pass defaultCollapsed and let the component manage itself internally.

Yes. Each Sidebar.Item takes an icon prop that accepts a component reference — icon={Home} — and the Sidebar renders it at the right size for you, so there is no <Home /> boilerplate at the call site. It accepts any icon that takes a className, which includes every lucide-react icon and most other React icon libraries. You can also pass an already-rendered element if you need to preset props on the icon.

Yes. The component depends only on React, the local cn utility, and lucide-react for its two built-in icons — no Next.js API and no router — so it works anywhere React and Tailwind reach the DOM. Set the project up with TypeScript and Tailwind v4 first, install lucide-react, then run npx drivn add sidebar and compose the rail from its dot-notation parts the same way.