Accordion
An animated accordion / FAQ expander with a bouncy transition, supporting single-open and multi-open modes.
Height expansion is driven by a spring physics model that bounces back as it settles; the content slides up slightly at the same time, keeping the layers distinct.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add accordion.enAccordion is a React accordion (collapse) component built with Motion and Tailwind CSS, often used for FAQ sections. Each row is described in an items array, and you can allow one or several rows open at a time.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<Accordion/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- FAQs at the bottom of a product or pricing page
- Steps or extra notes inside help center articles
- Grouping advanced options out of the way on a settings page
- Specs, shipping, and returns information on a product page
- Not a good fit: a full FAQ section with search filtering and category tabs; use FAQ Accordion Section instead
Installation
npx webberui@latest add https://webberui.com/r/accordion.en.jsonOr, once registries are configured in components.json, install it as @webberui/accordion.
Usage
import { Accordion } from "@/components/ui/accordion";
<Accordion
defaultValue="a"
items={[
{ value: "a", title: "First question", content: "The answer to the first question." },
{ value: "b", title: "Second question", content: "The answer to the second question." },
]}
/>;For multi-open mode pass multiple, and defaultValue can take an array of strings:
<Accordion multiple defaultValue={["a", "b"]} items={items} />Examples
FAQ page in the Next.js App Router
Accordion is a client component, so you can render it straight from a Server Component page and map server-fetched text into items.
// app/faq/page.tsx (Server Component)
import { Accordion } from "@/components/ui/accordion";
import { getFaqs } from "@/lib/faqs";
export default async function FaqPage() {
const faqs = await getFaqs();
return (
<Accordion
defaultValue={faqs[0]?.id}
items={faqs.map((faq) => ({
value: faq.id,
title: faq.question,
content: faq.answer,
}))}
/>
);
}Links and lists in the content
Both title and content accept React nodes, so links, lists, or other components work.
<Accordion
items={[
{
value: "refund",
title: "Can I get a refund?",
content: (
<>
You can request a refund within 7 days of your order. See the{" "}
<a href="/refund-policy" className="underline">refund policy</a>.
</>
),
},
{
value: "invoice",
title: "How do I get an invoice?",
content: (
<ul className="list-disc pl-5">
<li>Individuals: an e-invoice is sent to your account email</li>
<li>Companies: enter your tax ID at checkout</li>
</ul>
),
},
]}
/>Multi-open settings panel without bounce
Settings screens usually want calmer motion. Use bounce={false} for a smooth fade, and adjust the frame with className.
<Accordion
multiple
bounce={false}
className="rounded-none border-x-0"
items={[
{ value: "notify", title: "Notifications", content: <NotificationSettings /> },
{ value: "privacy", title: "Privacy", content: <PrivacySettings /> },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | AccordionItemData[] | — | Data for each row, containing value, title, and content |
multiple | boolean | false | Allows several rows to be expanded at once |
defaultValue | string | string[] | — | The value(s) expanded by default |
bounce | boolean | true | Whether the transition carries a bounce; turning it off uses a smooth fade in |
className | string | — | Forwarded to the outermost container |
AccordionItemData fields: value (unique identifier), title (ReactNode), content (ReactNode).
Accessibility
- The trigger is a native
buttoncarryingaria-expandedandaria-controls; the content area is aregionpointing back at the trigger witharia-labelledby - With "reduce motion" enabled at the system level, it degrades to a brief smooth fade in with no height bounce
FAQ
Does it work with the Next.js App Router?
Yes. The file already starts with "use client", so you can import it from a Server Component page as usual. The reduced-motion check applies only after mounting, so the first render matches the SSR output.
Is collapsed content in the HTML?
No. Only open rows render their content region; collapsing a row removes its content from the DOM. If you want certain answers in the HTML on page load, include them in defaultValue.
Can I control which row is open programmatically?
Only uncontrolled mode is supported for now, and defaultValue is read on the first render only. To reset the open state, change the component's key so it remounts.
How do I turn off the bounce?
Set bounce={false} to use a smooth fade of about 0.2 seconds; the same transition is used automatically when the user has reduced motion enabled.
Does it support keyboards and screen readers?
Each row title is a native button inside an h3, with aria-expanded and aria-controls, so Tab moves between rows and Enter or Space toggles them. The content area has role="region" and points back to its title with aria-labelledby.