Next.js Sidebar with the App Router
Add a collapsible sidebar to a Next.js App Router app. Drivn ships a copy-paste, dot-notation nav rail with usePathname active links and zero runtime deps.
A sidebar is the persistent navigation rail down the edge of an app — the column of links, grouped sections, and a collapse toggle that stays put while the main panel swaps. In a Next.js App Router project the first thing to know about Drivn's Sidebar is that it is a client component: the root file opens with 'use client' because it tracks the collapsed state in React.useState and shares it through a context that Sidebar.Item, Sidebar.Group, and Sidebar.CollapseButton all read. That client boundary is small and deliberate — you drop the rail into your layout.tsx and the <main> beside it keeps rendering on the server.
After install the component lives at src/components/ui/sidebar.tsx as a dot-notation compound: Sidebar is the <aside> root, and Sidebar.Header, Sidebar.Content, Sidebar.Footer, Sidebar.Group, Sidebar.Item, Sidebar.Separator, and Sidebar.CollapseButton compose the rest. The root width animates between w-[240px] expanded and w-[60px] collapsed via transition-[width], and a variant prop toggles a bordered default rail or a floating card. There are no Radix packages behind it — every class is Tailwind, every piece of state is plain React.
This guide installs Drivn in a Next.js 16 project, composes the sidebar into an App Router layout, highlights the active route with usePathname, and restyles the widths and variant. For the full API see the Sidebar docs; for the shadcn/ui comparison see Drivn vs shadcn/ui Sidebar.
Install in a Next.js 16 project
Drivn installs through a small CLI that writes component source directly into your repository — there is no runtime package to version-lock. From the root of your Next.js 16 project run npx drivn add sidebar. The Sidebar imports only React, two lucide-react icons (ChevronDown and PanelLeft), and the local cn utility, so the CLI writes a single sidebar.tsx file and leaves the rest of your tree untouched. The CLI reference documents every flag, including targeting a custom directory or installing several components at once. After install you own the file, and future Drivn releases will not overwrite it — commit it for a clean baseline before wiring it into a layout. Because the rail is a self-contained compound with no peer dependency past lucide-react, there is nothing else to configure as long as Tailwind v4 is already processing your styles.
1 # from the root of your Next.js 16 project 2 npx drivn add sidebar
Highlight the active route with usePathname
The App Router's usePathname hook makes active-link highlighting a one-liner. In your client nav component, read the current path and pass active={pathname === href} to each Sidebar.Item. The active prop swaps in styles.itemActive — bg-accent text-foreground font-medium — so the current section reads as selected without any manual class juggling. Because usePathname is a client hook, this is exactly why the rail lives behind the 'use client' boundary: the toggle state and the route read both belong on the client, while everything else in the layout stays on the server. Wrap each Sidebar.Item in a Next.js <Link> (or hand it an onClick router push) so navigation runs through the App Router's client cache. Pair the rail with a Breadcrumb in the header to echo the same path.
1 'use client' 2 import { usePathname } from 'next/navigation' 3 import { Sidebar } from '@/components/ui/sidebar' 4 import { Home, Inbox, Users } from 'lucide-react' 5 6 export function SideNav() { 7 const pathname = usePathname() 8 return ( 9 <Sidebar> 10 <Sidebar.Header> 11 <span className="text-sm font-semibold">Drivn</span> 12 </Sidebar.Header> 13 <Sidebar.Content> 14 <Sidebar.Item icon={Home} active={pathname === '/dashboard'}> 15 Dashboard 16 </Sidebar.Item> 17 <Sidebar.Item icon={Inbox} badge={3} active={pathname === '/inbox'}> 18 Inbox 19 </Sidebar.Item> 20 <Sidebar.Item icon={Users} active={pathname === '/team'}> 21 Team 22 </Sidebar.Item> 23 </Sidebar.Content> 24 </Sidebar> 25 ) 26 }
Collapse the rail and group your links
Drop a Sidebar.CollapseButton into the header and the rail gains a rail-width toggle for free — it reads and flips the collapsed state from context and rotates its PanelLeft icon 180 degrees. Collapsing animates the root <aside> between w-[240px] and w-[60px] through transition-[width], hides each item's label, and centers the icons, turning the full nav into an icon rail. For longer navigation, wrap related items in a Sidebar.Group with a heading — the group is collapsible on its own, animating open and closed by transitioning its grid template rows between 1fr and 0fr rather than measuring pixel heights. Use Sidebar.Separator to divide unrelated groups. The verbatim group panel below shows the grid-rows technique the component uses, copied from the registry source.
1 // verbatim from sidebar.tsx — the group collapse panel 2 <div 3 className={styles.groupPanel} 4 style={{ gridTemplateRows: !heading || open || collapsed ? '1fr' : '0fr' }} 5 > 6 <div className={styles.groupContent}> 7 {children} 8 </div> 9 </div>
Restyle the widths and the floating variant
Because you own sidebar.tsx after install, every dimension is a one-line edit. The styles.width object holds expanded: 'w-[240px]' and collapsed: 'w-[60px]' — widen the expanded rail or shrink the icon rail by changing those two strings. The variant prop selects between default (a border-r rail flush against the content) and floating (a border rounded-xl shadow-lg m-2 card that hovers inside the layout), so <Sidebar variant="floating"> turns the rail into a detached panel with no other change. Pass a side="right" prop to move the rail to the opposite edge. The verbatim root render below shows how the base class, the variant, and the current width merge through cn(). For deeper palette control, the accent and border tokens the rail reads come from theming, so retinting them re-themes the sidebar in dark and light mode at once.
1 // verbatim from sidebar.tsx — the root <aside> render 2 <aside 3 className={cn(styles.base, styles.variants[variant], collapsed ? styles.width.collapsed : styles.width.expanded, className)} 4 > 5 {children} 6 </aside>
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
It is a client component. The root file ships with a 'use client' directive because it stores the collapsed state in React.useState and shares it through a context that the items, groups, and collapse button all read. In the Next.js App Router you keep your layout.tsx a Server Component and render the sidebar as a nested client island, so only the rail ships JavaScript while the <main> beside it stays server-rendered.
Read the current path with the App Router's usePathname hook inside your client nav component, then pass active={pathname === href} to each Sidebar.Item. The active prop applies bg-accent text-foreground font-medium, so the current section reads as selected. Wrap each item in a Next.js <Link> so navigation runs through the client cache, and the highlight updates automatically as the path changes.
The Sidebar.CollapseButton flips a collapsed boolean stored in context. When it toggles, the root <aside> animates between w-[240px] and w-[60px] through a transition-[width] class, item labels hide, and icons center to form a compact icon rail. Collapsible Sidebar.Group sections animate separately by transitioning their CSS grid template rows between 1fr and 0fr, which avoids measuring pixel heights in JavaScript.
Yes. The root accepts a side prop that defaults to 'left'; pass side="right" to move the rail to the opposite edge. You own the sidebar.tsx file after install, so any further layout tweak — a wider expanded width, a different border, a floating card — is a one-line edit to the styles object. The variant="floating" prop also detaches the rail into a rounded, shadowed panel.
No. The Sidebar is pure React and Tailwind — its only imports past React are two lucide-react icons and the local cn utility. There is no Radix package, no cva, and no external state library. The collapse state is React.useState, the shared context is a plain React.createContext, and every class lives in a co-located styles object, so the entire component is readable in one file you fully own.

