Drivn vs shadcn/ui — Sidebar Compared
Compare Drivn Sidebar vs shadcn/ui — a single-file compound with width-transition collapse versus a multi-part Radix setup with a SidebarProvider.
Drivn's Sidebar and shadcn/ui's Sidebar both give you a left- or right-side navigation rail that collapses, holds groups, and renders icon-labelled items, but the implementations are on opposite ends of the spectrum. shadcn/ui ships a roughly twenty-five-export sidebar package — SidebarProvider, SidebarTrigger, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarRail, plus a Sheet for the mobile drawer, all wired together through a context provider that reads a cookie to remember the collapsed state. The dependency graph pulls in @radix-ui/react-slot, the Sheet primitives, and the rest of the shadcn primitives that those touch.
Drivn's Sidebar is one file at @/components/ui/sidebar.tsx with seven dot-notation members — Sidebar.Header, Sidebar.Content, Sidebar.Footer, Sidebar.Group, Sidebar.Item, Sidebar.Separator, and Sidebar.CollapseButton — exported through an Object.assign on the root function. The collapsed state is a single React.useState boolean with an optional controlled override. The animation is a Tailwind transition-[width] between w-[240px] and w-[60px]. There is no provider higher up the tree, no cookie, no Sheet, no Radix Slot.
The practical split is a single-file compound versus a many-file package with a global provider. This page walks the API surface, the collapse mechanic, the group animation, and the bundle footprint so you can pick the flavour that fits.
Side-by-side comparison
| Feature | Drivn | shadcn/ui |
|---|---|---|
| Files installed | One — sidebar.tsx | Sidebar package + Sheet + provider (multi-file) |
| Runtime UI dependencies | lucide-react + cn() utility | @radix-ui/react-slot, Sheet primitives |
| Public API surface | 7 dot-notation members on Sidebar | ~25 named exports (Provider, Trigger, Inset, Menu, …) |
| Collapse mechanism | transition-[width] between 240px and 60px | CSS variables + data-state + Sheet on mobile |
| State persistence | In-memory React state (controlled or uncontrolled) | Cookie read by SidebarProvider |
| Provider required | No — Sidebar is the root | Yes — <SidebarProvider> at the layout |
| Variant prop | 'default' | 'floating' | 'sidebar' | 'floating' | 'inset' |
| Mobile behaviour | Same collapse — width transitions to 60px | Switches to a Sheet drawer |
| License | MIT | MIT |
| Copy-paste install |
One file, seven dot-notation members
shadcn/ui's Sidebar pulls in around twenty-five named exports — Sidebar, SidebarProvider, SidebarTrigger, SidebarRail, SidebarInset, SidebarHeader, SidebarFooter, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarGroupAction, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarMenuAction, SidebarMenuBadge, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, SidebarInput, SidebarSeparator, plus the useSidebar hook. Every name lands at the top of your import statement.
Drivn collapses all of that into seven dot-notation members on a single root: Sidebar.Header, Sidebar.Content, Sidebar.Footer, Sidebar.Group, Sidebar.Item, Sidebar.Separator, and Sidebar.CollapseButton. The export is one line at the bottom of the file — Object.assign(SidebarRoot, { Header, Content: SidebarContent, Footer, Group, Item, Separator, CollapseButton }). Your import statement stays at import { Sidebar } from "@/components/ui/sidebar". The Dialog, Select, and Accordion follow the same dot-notation rule, so the shape is consistent across the library.
1 // Drivn — one import, seven members 2 import { Sidebar } from "@/components/ui/sidebar" 3 import { Home, Inbox, Users } from "lucide-react" 4 5 <Sidebar> 6 <Sidebar.Header> 7 <span className="text-sm font-semibold">Drivn</span> 8 <Sidebar.CollapseButton /> 9 </Sidebar.Header> 10 <Sidebar.Content> 11 <Sidebar.Group heading="Menu"> 12 <Sidebar.Item icon={Home} active>Dashboard</Sidebar.Item> 13 <Sidebar.Item icon={Inbox} badge={3}>Inbox</Sidebar.Item> 14 <Sidebar.Item icon={Users}>Team</Sidebar.Item> 15 </Sidebar.Group> 16 </Sidebar.Content> 17 </Sidebar>
The collapse mechanic — width transition, no provider
shadcn/ui's Sidebar wraps everything in a SidebarProvider at the root of your layout. The provider reads a cookie to restore the last collapsed state, exposes a useSidebar hook for the trigger, and decides whether to render the desktop rail or the mobile Sheet drawer based on a media query. The state lives in context above the sidebar, not in the sidebar itself.
Drivn's Sidebar is the root. The collapsed state is a single React.useState inside SidebarRoot, with an optional controlled override via the collapsed and onCollapsedChange props. There is no provider higher up the tree, no cookie, and no media query branch — the same component renders on every breakpoint. The animation is a Tailwind transition-[width] duration-200 ease-in-out applied to the <aside>, swapping between w-[240px] when expanded and w-[60px] when collapsed. Items hide their label and badge via the same collapsed context that the header reads, so the rail compresses to icons without remounting anything.
1 // Drivn — the collapse classes, verbatim from the registry 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 width: { 8 expanded: 'w-[240px]', 9 collapsed: 'w-[60px]', 10 }, 11 }
Group collapsing through grid-template-rows, not max-height
A common React trick for animating a section open and closed without measuring its height is to wrap the content in a CSS Grid container and animate grid-template-rows from 0fr to 1fr. The browser handles the layout, the animation stays smooth at any content size, and there is no requestAnimationFrame loop reading scrollHeight. Drivn's Sidebar.Group uses that exact technique. The group panel sets grid transition-[grid-template-rows] duration-200, and the inline style swaps gridTemplateRows: open ? "1fr" : "0fr". The inner content gets overflow-hidden, so the children clip during the transition.
The heading row above the panel is a <button> when collapsible is true, with a ChevronDown icon that rotates 180 degrees when open. When the entire sidebar is collapsed, the heading hides and the panel snaps to 1fr regardless of open, so collapsed-rail mode never shows half-closed groups. The whole thing is roughly thirty lines in the source — same file, same import — and the Accordion component uses the same grid-template-rows trick if you want the technique on its own.
1 // Drivn — the group panel, verbatim from the registry 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>
Bundle weight and what ships with the file
shadcn/ui's Sidebar setup imports @radix-ui/react-slot for the asChild pattern, the Sheet primitives (which themselves import @radix-ui/react-dialog), and the rest of the shadcn primitives that those touch. Every one of those Radix packages ships its own bundle, and they all land in the route that uses the Sidebar.
Drivn's Sidebar imports React, two icons from lucide-react (ChevronDown and PanelLeft), and the local cn() utility. The icons land in the bundle only because they appear in the JSX — every Drivn component pulls icons the same way, so you pay for them once across the library. There is no Radix Slot, no Sheet, no second component file. After the Drivn CLI writes the file into your project, the entire component lives at @/components/ui/sidebar.tsx and you can read the full surface in one editor tab. To split a layout body from a sidebar-driven settings region, pair the component with a Separator or the Card compound.
1 // Drivn — the full import header at the top of sidebar.tsx 2 'use client' 3 4 import * as React from 'react' 5 import { ChevronDown, PanelLeft } from 'lucide-react' 6 import { cn } from '@/utils/cn'
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. Drivn's Sidebar is the root component — the collapsed state lives inside SidebarRoot as a single React.useState, with an optional controlled override through the collapsed and onCollapsedChange props. You drop a Sidebar straight into your layout and it works. shadcn/ui requires a SidebarProvider higher up the tree to handle cookie persistence and the desktop-vs-mobile branch; Drivn skips both because the same width transition runs on every breakpoint.
It uses the same width transition as on desktop. The aside animates from w-[240px] to w-[60px] via Tailwind's transition-[width] utility, and items hide their labels through the collapsed context. There is no separate Sheet drawer component. If you want a full off-canvas drawer on small screens, hide the Sidebar with a media-query className and render a Drivn Dialog or your own off-canvas pattern — the file is yours after install, so a one-line change to the responsive classes is enough.
Yes — the component is controlled-or-uncontrolled, so you wire persistence wherever your app already stores user preferences. Pass collapsed and onCollapsedChange in from a parent that reads and writes localStorage, a cookie, or a server-side user profile. Drivn deliberately does not bake the cookie read into the component because the persistence layer is project-specific (and on the server, cookies pull a layout into the client boundary).
Sidebar.Group wraps its children in a CSS Grid container with grid-template-rows transitioning between 0fr and 1fr. The browser handles the layout math, so the animation stays smooth at any content size without measuring scrollHeight. The inner content has overflow-hidden to clip during the transition, and the chevron icon in the heading rotates 180 degrees. The same trick powers the Accordion component.
Two patterns. Inside the Sidebar header you can drop a Sidebar.CollapseButton — a single-icon button wired to the same collapsed context that toggles the rail. Outside the Sidebar (in a top header, for example) you control collapse from the parent that owns the collapsed state: pass collapsed and onCollapsedChange into the Sidebar, then render any button you like that calls setCollapsed(!collapsed). Both shapes ship in the registry source.

