Next.js Accordion — Zero-Dep Component for App Router
Add a Next.js Accordion with zero runtime UI dependencies. Drivn ships a single-file component with dot notation and App Router server-rendered support.
Next.js 16 renders on the server by default, which puts an interactive primitive like an accordion in an awkward spot: the panel that opens and closes needs client state, but the page, layout, and copy around it can stay on the server and ship zero JavaScript. The component you reach for should draw that client boundary as tightly as possible — and stay small enough to read top to bottom the one time you open the file.
Drivn's Accordion is shaped for that constraint. After npx drivn add accordion the source lands in src/components/ui/accordion.tsx, opens with 'use client', and pulls in nothing beyond React and lucide-react. Open panels live in a local Set, height animates through a CSS grid-template-rows transition rather than a JavaScript measurement loop, the chevron turns with a plain transform, and dot notation keeps every instance to a single import.
This guide covers installing Drivn in a Next.js 16 App Router project, mounting an Accordion on an FAQ or marketing route, nesting it inside a server-rendered layout, and seeding default-open panels from URL search params so a link can deep-link straight to an expanded item. Every snippet comes from the component's real API — no forks, no extra config. For the full reference see the Accordion docs; for the head-to-head, see Drivn vs shadcn/ui Accordion.
Install in a Next.js 16 project
Drivn installs through a tiny CLI that writes component source files directly into your repository — no runtime npm package, no version lock to upgrade. Open a terminal in the root of your Next.js 16 project and run npx drivn add accordion. The CLI prompts you once for your install directory (defaulting to src/components/ui/) and copies the accordion source file plus the lucide-react dependency it needs for the chevron icon. After install you own the file — future Drivn releases will not overwrite it, and you can edit the internals without forking a package. The CLI reference documents every flag, including how to target a custom path or install multiple components at once. If your Next.js project already uses lucide-react, no second install is triggered. Commit the change so you have a clean baseline before customizing.
1 # from the root of your Next.js 16 project 2 npx drivn add accordion 3 4 # install related primitives at the same time if needed 5 npx drivn add button card
Place inside an App Router layout
An App Router page is a server component by default, which means you cannot call useState or useEffect inside it. Drivn's Accordion is marked 'use client' at the top of the source file, so Next.js treats it as a client boundary. That boundary is local — the surrounding layout, headers, and non-interactive content below can remain server-rendered. A common pattern is a server-rendered FAQ page whose only client island is the Accordion itself, which keeps JavaScript payload minimal for the rest of the route. Import the component directly from @/components/ui/accordion and drop it into any server page — no dynamic() import, no SSR-disable flag, no extra Suspense boundary. The installation guide covers project bootstrapping; the Accordion docs show every prop.
1 // app/faq/page.tsx — server component 2 import { Accordion } from '@/components/ui/accordion' 3 4 export const metadata = { 5 title: 'FAQ · Drivn', 6 description: 'Questions and answers about Drivn.', 7 } 8 9 export default function FaqPage() { 10 return ( 11 <main className="mx-auto max-w-2xl py-12"> 12 <h1 className="text-4xl font-bold mb-8">FAQ</h1> 13 <Accordion> 14 <Accordion.Item value="install"> 15 <Accordion.Trigger>How do I install Drivn?</Accordion.Trigger> 16 <Accordion.Content> 17 Run npx drivn@latest create in your project root. 18 </Accordion.Content> 19 </Accordion.Item> 20 <Accordion.Item value="license"> 21 <Accordion.Trigger>What is the license?</Accordion.Trigger> 22 <Accordion.Content> 23 MIT. Copy the source and ship. 24 </Accordion.Content> 25 </Accordion.Item> 26 </Accordion> 27 </main> 28 ) 29 }
Sync default open state with the URL
A common UX request for FAQ and settings pages is to bookmark a specific open panel — sharing ?section=billing should land on a page with that item expanded. Because Next.js App Router exposes search params through a server-component prop, you can read the requested section on the server and pass it into the Accordion as defaultValue. No client-side store, no useSearchParams hook, no extra re-render. For pages where users toggle panels and expect the URL to update in response, wrap the Accordion in a thin client component that calls router.replace inside each trigger's click handler. Keep the server part doing the initial render — only opt into client behavior where the URL needs to change on user action. For form-driven stepper URLs, the React Hook Form guide shows a similar pattern.
1 // app/faq/page.tsx 2 import { Accordion } from '@/components/ui/accordion' 3 4 type Props = { 5 searchParams: Promise<{ section?: string }> 6 } 7 8 export default async function FaqPage({ searchParams }: Props) { 9 const { section = 'install' } = await searchParams 10 return ( 11 <Accordion key={section} defaultValue={section}> 12 <Accordion.Item value="install"> 13 <Accordion.Trigger>How do I install Drivn?</Accordion.Trigger> 14 <Accordion.Content>Run npx drivn add.</Accordion.Content> 15 </Accordion.Item> 16 <Accordion.Item value="license"> 17 <Accordion.Trigger>What is the license?</Accordion.Trigger> 18 <Accordion.Content>MIT. Copy and ship.</Accordion.Content> 19 </Accordion.Item> 20 </Accordion> 21 ) 22 }
SSR-safe rendering and hydration
Drivn's Accordion seeds its open state from the defaultValue prop, not from localStorage or any browser-only API, so the very first render produces identical HTML on the server and the client. That is what keeps Next.js from logging the hydration-mismatch warning it prints whenever a component paints different markup across the two environments. If you want the open panel to survive a reload, read the stored value on the server from a cookie and hand it down as defaultValue; the client then hydrates an accordion that is already in the right state, with no post-mount reflow.
Keep browser reads like window.localStorage out of the component body — a server-side cookie read passed through props is both cleaner and mismatch-proof. The animation needs no client measurement either: the grid-template-rows transition is pure CSS that runs once the element hydrates, so server and client output stay byte-identical. For the ground-up API, the Accordion docs list every prop, and the installation guide covers wiring Drivn into an existing project.
Styling with Tailwind v4 tokens
Drivn assumes you use Tailwind v4 with an inline @theme block where design tokens are declared as CSS custom properties. The Accordion reads tokens like --foreground, --muted-foreground, and --border for its surface colors and chevron tint. In a Next.js 16 project scaffolded with npx drivn@latest create, the tokens are already wired up — the component renders correctly in both dark and light mode without touching any Tailwind config. If you bring Drivn into an existing Next.js project with its own token names, the accordion file is the only place you need to adjust. Open src/components/ui/accordion.tsx, find the styles object near the top, and swap token names in the trigger, icon, and content strings to match your theme. The theming page maps every Drivn token to its default HSL value so you can rename or replace in one pass.
1 // src/components/ui/accordion.tsx — styles object 2 const styles = { 3 base: 'w-full divide-y divide-border border-y border-border', 4 trigger: cn( 5 'flex items-center justify-between w-full py-4', 6 'text-sm font-medium text-foreground', 7 'hover:text-foreground/80 transition-colors cursor-pointer' 8 ), 9 icon: cn( 10 'w-4 h-4 text-muted-foreground', 11 'transition-transform duration-200' 12 ), 13 panel: 'grid transition-[grid-template-rows] duration-200', 14 content: 'overflow-hidden text-sm text-muted-foreground', 15 }
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
The Accordion itself is a client component — it is marked with 'use client' at the top of the file and manages open state with React hooks. Your surrounding layout, page, and non-interactive markup can still render on the server. Import and use the Accordion directly inside any server page; Next.js inserts the client boundary automatically when the import crosses from server to client code.
Yes. Read search params from the page's searchParams prop, destructure the section value, and pass it to the Accordion's defaultValue. Because state initializes from props, the server renders the correct open panel on the first request — no hydration mismatch and no post-mount re-layout. For pages that should also update the URL when users toggle panels, add a thin client wrapper that calls router.replace inside each trigger handler.
No. The component renders safely without dynamic imports, SSR-disable flags, or extra Suspense boundaries. It uses only React state and CSS — there are no browser-only APIs in the render path, so the server-rendered HTML matches the client-rendered HTML exactly. Next.js handles the client boundary automatically when it encounters the 'use client' directive at the top of the Accordion source file.
The panel is a CSS grid whose grid-template-rows transitions from 0fr to 1fr when opened. Browsers interpolate fractional row heights natively, so content of any size animates smoothly without a ResizeObserver, an effect hook, or a measurement loop. The chevron rotation is a simple CSS transform tied to the open state attribute. Both animations run in pure CSS once the element hydrates, keeping JavaScript cost effectively zero.

