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 pushes teams toward server components by default, which creates a small puzzle for interactive primitives like an accordion: the root needs client-side state to track open panels, but the surrounding layout can stay on the server. You want a component that opts into "use client" only where it needs to — and a file small enough to read in one pass when you do open it.
Drivn's Accordion was built for exactly that shape. The component lives in src/components/ui/accordion.tsx after install, marks itself as a client component, and carries no runtime UI dependencies besides React and lucide-react. Open state sits in a local Set, the panel height animates via a CSS grid-template-rows transition driven by an inline style, the chevron rotates with a CSS transform, and the dot-notation API keeps every accordion instance to a single import statement.
This guide walks through installing Drivn in a Next.js 16 App Router project, scaffolding an Accordion on a marketing or FAQ page, placing it inside a server-component layout, and wiring default-open state from URL search params for bookmarkable panels. Every code block is drawn from the component's actual API — nothing here requires a custom fork or extra config. For a ground-up reference, see the Accordion docs; for the shadcn/ui comparison, 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
Because Drivn's Accordion initializes its internal state from the defaultValue prop rather than from localStorage or browser-only APIs, its first render is identical on the server and the client. That avoids the hydration-mismatch warnings Next.js prints when a component renders different HTML between environments. If your design calls for persisting the open item across page loads, read the stored value on the server (via a cookie) and pass it as defaultValue — the client then receives a fully rendered accordion with no post-mount re-layout. Avoid reading window.localStorage inside the component; the cleaner path is a server-side cookie read passed down through props. For animations that depend on client-only measurements, the Drivn accordion has none — the CSS grid row transition runs in pure CSS once the element hydrates, keeping server and client output byte-identical.
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.