Build a FAQ accordion for a Next.js landing page
Published: September 11, 2026 · 11 min read
How to put a FAQ accordion on a Next.js landing page. Three paths: details/summary, one-open-at-a-time state, or a finished section. Why FAQs go unread, and what to check before you ship.
Up front: a FAQ works when the closed headings do the job
In short: a Next.js FAQ works when closed questions are scannable before answers get long. Fully open copy is a help center. Five to eight items, one line each. That shape gets read after pricing.
Three ways: HTML details and summary. State that keeps one item open. Mount a finished FAQ section. You do not need a FAQ library to start.
By the end you can ship one FAQ block at the bottom of an LP.
Three reasons a FAQ goes unread
Dumping every answer open at the footer is not wrong. If a scroller cannot tell in one second whether their worry is here, they never reach contact.
1. Questions that are preambles. Closed rows should be words visitors say. “Can I try it free?” beats a sentence about inquiries.
2. Everything already open. Eight open items is an article, not an accordion. Long copy after pricing washes out the numbers.
3. Bad placement. Suddenly before the footer, people who have not compared anything hit “FAQ” first. After value and price, just before the last CTA, is easier.
Target FAQ for this article
Aim for this.
Heading can be “FAQ”. One line of lead-in at most. Questions in a stack. Closed: question plus a chevron. Open: a short paragraph. One item open at a time. On phones, wrap the question without crushing the tap target.
Not a searchable help center. Five to eight worries on an LP.
Three ways to get there
Same finish, different cost.
Method 1 is details / summary. No JavaScript. Stays a Server Component. Several can be open at once.
Method 2 is an accordion with one open value. Needs "use client". For keyboard support, Radix or shadcn/ui Accordion is faster.
Method 3 mounts a FAQ section that already has spacing, open/close, and heading layout. You swap questions and answers. Not for a 100-item support site that needs search.
Unsure: ship method 1 with five items. If simultaneous opens bother you, go to 2 or 3.
Method 1: Open and close with details and summary
Start as a Server Component. Native details already covers basic keyboard and screen readers. Tailwind adds rules and spacing.
Keep summary to one line
Two sentences in summary make the closed list hard to scan. Question: short, no period. Answer: one to three sentences in a p. Links live in the answer only.
const items = [
{
q: "Can I try it free?",
a: "Live preview needs no card. Source copy is free 3 times per day, Japan time.",
},
{
q: "Can I drop this into an existing Next.js app?",
a: "Sections assume React and Tailwind. Add missing npm deps to package.json.",
},
];
export function Faq() {
return (
<section className="mx-auto max-w-2xl px-6 py-24">
<h2 className="text-3xl font-semibold">FAQ</h2>
<div className="mt-10 divide-y divide-zinc-200 border-y">
{items.map((item) => (
<details key={item.q} className="group py-4">
<summary className="cursor-pointer list-none pr-8 text-left font-medium">
{item.q}
</summary>
<p className="mt-3 text-sm leading-relaxed text-zinc-600">{item.a}</p>
</details>
))}
</div>
</section>
);
}What this stage still lacks
Method 1 lets several details stay open. If the LP should close the rest after one pricing worry, use method 2. A rotating chevron can be CSS group-open. If motion is the point, method 3 is faster.
Method 2: Allow only one item open
An LP FAQ is not a comparison table. It clears one worry. In Next.js, mark only the FAQ file "use client". Do not make the whole page a Client Component.
Weak vs strong
Weak: a useState per question, all independent. Same as method 1.
Strong: one open id. Click the same control again to close. The eye stays on one item.
"use client";
import { useState } from "react";
const items = [
{ id: "free", q: "Is there a free plan?", a: "Browse and preview are free. Copy quota is 3 times a day." },
{ id: "stack", q: "Does it work next to shadcn/ui?", a: "Yes. Add FAQ as a section and map colors to existing tokens." },
];
export function FaqExclusive() {
const [open, setOpen] = useState<string | null>("free");
return (
<section className="mx-auto max-w-2xl px-6 py-24">
<h2 className="text-3xl font-semibold">FAQ</h2>
<div className="mt-10 divide-y">
{items.map((item) => {
const isOpen = open === item.id;
return (
<div key={item.id} className="py-4">
<button
type="button"
aria-expanded={isOpen}
onClick={() => setOpen(isOpen ? null : item.id)}
className="w-full text-left font-medium"
>
{item.q}
</button>
{isOpen ? (
<p className="mt-3 text-sm text-zinc-600">{item.a}</p>
) : null}
</div>
);
})}
</div>
</section>
);
}When to use shadcn/ui Accordion
Arrow keys, type="single", and animation are faster with Accordion than a raw button. It is a primitive. Left heading plus right list, dark background, easing — that is an LP FAQ section, not the primitive. Want a frame: shadcn. Want a finished block: method 3.
Method 3: Mount a finished FAQ section
Heading on the left, accordion on the right, answers expanding down, dark background after pricing. That block can take half a day after method 2’s state.
Watch a real FAQ in the browser, then mount a React + Tailwind section in your Next.js app. DesignLayer is that shelf. Browse with no card. https://design-layer.com/en/components
Fit first: SaaS, tools, subscription LPs. Not a 100-question support site. That is searchable docs.
Three jobs: watch open/close tempo live. Put source in the repo. Swap questions, answers, heading. Do not rebuild icons and spacing.
Free source copy is 3 times per day, Japan time. One FAQ today is enough. Pricing: https://design-layer.com/en/articles/nextjs-pricing-table-guide — hero: https://design-layer.com/en/articles/nextjs-animated-hero-section-guide — do not add loud entrance motion on the FAQ. This is a reading spot.
Pre-launch checklist
After motion, check five things.
1. Closed questions scan. Do not put answer excerpts in summary.
2. Not all open on load. One default-open item is fine. Pricing or free-quota worry belongs first.
3. summary / button at least 44px on phones. Do not make only the gap between rules the hit target.
4. If you add FAQPage JSON-LD, it matches visible Q and A. Do not hide copy in JSON only.
5. Do not put "use client" on the whole page.tsx. Isolate the FAQ.
Common failures
1. Internal jargon in questions. “What’s the Copy for Cursor quota?” loses to “How many copies per day?”
2. New sales copy in answers. A FAQ clears worry. It is not a second hero.
3. Twenty items. They are not read on an LP. Keep what ads and pricing actually trigger. Link the rest to docs.
FAQ
Questions that keep coming up in implementation.
What do I need for a FAQ accordion in Next.js?
details and summary are enough at first. One-open-at-a-time needs one client FAQ component. Spacing and motion included: a finished section.
How many questions?
Five to eight on an LP. Three is fine. Twelve or more belong on a help page.
Can this stay a Server Component?
details can. If open id is state, mark only the FAQ file "use client".
Isn’t shadcn/ui Accordion enough?
As the open/close control, yes. Two-column heading, background, timing you add. A primitive and a finished FAQ section are different jobs.
Should everything stay open for SEO?
Answer text in the HTML is usually enough even when closed. details keeps answers in the DOM. If you render answers only when open, check crawlers separately. Do not lie in JSON-LD vs the page.
Do I need FAQPage structured data?
Search can show FAQ rich results. If you add it, only questions and answers on the page. Do not paste another site’s copy into JSON.
Accessibility?
Toggle is a button or summary, not a div onClick. Keep aria-expanded in sync. Decorative icons aria-hidden. Do not skip heading levels on questions.
Where in an existing page.tsx?
After the pricing table, before the last CTA or footer. Right after the hero dumps a worry list on people who are not ready. Right after numbers is easier. Pricing: https://design-layer.com/en/articles/nextjs-pricing-table-guide