Skip to main content

Accordion

Toggle content sections. Keep layouts clean. Focus user attention.

Every teammate you invite takes a seat. New seats are prorated for the days left in the billing period, and seats you remove come back as credit on the next invoice.
1'use client';
2
3import { Accordion } from '@/components/ui/accordion';
4
5const faq = [
6 {
7 value: 'seats',
8 question: 'How are seats billed?',
9 answer:
10 'Every teammate you invite takes a seat. New seats are prorated for the days left in the billing period, and seats you remove come back as credit on the next invoice.',
11 },
12 {
13 value: 'plan-change',
14 question: 'Can I switch plans in the middle of a cycle?',
15 answer:
16 'Yes. Upgrades apply right away and you pay the difference for the rest of the month. Downgrades take effect on the renewal date so you keep what you already paid for.',
17 },
18 {
19 value: 'invoices',
20 question: 'Where do I find past invoices?',
21 answer:
22 'Open Settings, then Billing. Every invoice is there as a PDF, and the billing contact gets a copy by email within a few minutes of each charge.',
23 },
24 {
25 value: 'overage',
26 question: 'What happens if we go over the included usage?',
27 answer:
28 'We keep serving traffic and add the extra usage to your next invoice at the rate listed on your plan. You get an email as soon as you pass 80% of the included quota.',
29 },
30];
31
32export function Default() {
33 return (
34 <Accordion type="single" className="w-full max-w-md" defaultValue="seats">
35 {faq.map((item) => (
36 <Accordion.Item key={item.value} value={item.value}>
37 <Accordion.Trigger value={item.value}>{item.question}</Accordion.Trigger>
38 <Accordion.Content value={item.value} className="text-muted-foreground text-sm">
39 {item.answer}
40 </Accordion.Content>
41 </Accordion.Item>
42 ))}
43 </Accordion>
44 );
45}

Installation

pnpm dlx nachui add accordion

Anatomy

1import { Accordion } from '@/components/ui/accordion';
1<Accordion type="single" defaultValue="item-1">
2 <Accordion.Item value="item-1">
3 <Accordion.Trigger>Is it accessible?</Accordion.Trigger>
4 <Accordion.Content>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.Content>
5 </Accordion.Item>
6</Accordion>

Composition

Use the following composition to build an Accordion:
Accordion
└── Accordion.Item
├── Accordion.Trigger
└── Accordion.Content

Variants

Single

The single type allows only one item to be open at a time.
Every teammate you invite takes a seat. New seats are prorated for the days left in the billing period, and seats you remove come back as credit on the next invoice.
1'use client';
2
3import { Accordion } from '@/components/ui/accordion';
4
5const faq = [
6 {
7 value: 'seats',
8 question: 'How are seats billed?',
9 answer:
10 'Every teammate you invite takes a seat. New seats are prorated for the days left in the billing period, and seats you remove come back as credit on the next invoice.',
11 },
12 {
13 value: 'plan-change',
14 question: 'Can I switch plans in the middle of a cycle?',
15 answer:
16 'Yes. Upgrades apply right away and you pay the difference for the rest of the month. Downgrades take effect on the renewal date so you keep what you already paid for.',
17 },
18 {
19 value: 'invoices',
20 question: 'Where do I find past invoices?',
21 answer:
22 'Open Settings, then Billing. Every invoice is there as a PDF, and the billing contact gets a copy by email within a few minutes of each charge.',
23 },
24 {
25 value: 'overage',
26 question: 'What happens if we go over the included usage?',
27 answer:
28 'We keep serving traffic and add the extra usage to your next invoice at the rate listed on your plan. You get an email as soon as you pass 80% of the included quota.',
29 },
30];
31
32export function Default() {
33 return (
34 <Accordion type="single" className="w-full max-w-md" defaultValue="seats">
35 {faq.map((item) => (
36 <Accordion.Item key={item.value} value={item.value}>
37 <Accordion.Trigger value={item.value}>{item.question}</Accordion.Trigger>
38 <Accordion.Content value={item.value} className="text-muted-foreground text-sm">
39 {item.answer}
40 </Accordion.Content>
41 </Accordion.Item>
42 ))}
43 </Accordion>
44 );
45}

Multiple

The multiple type allows multiple items to be open simultaneously.
1'use client';
2
3import { Accordion } from '@/components/ui/accordion';
4
5const causes = [
6 {
7 value: 'module-not-found',
8 trigger: 'Module not found during the build step',
9 content:
10 'The package resolves locally but not on the build machine. Move it out of devDependencies, commit the lockfile, and rerun the build with a cleared cache.',
11 },
12 {
13 value: 'out-of-memory',
14 trigger: 'Build ran out of memory',
15 content:
16 'Node stops at the default heap limit on large bundles. Set NODE_OPTIONS to --max-old-space-size=4096 in the project settings, then split the largest entry point if it happens again.',
17 },
18 {
19 value: 'missing-env',
20 trigger: 'Environment variable was undefined at build time',
21 content:
22 'Variables scoped to Preview are not injected into Production builds. Check the scope column in Settings, Environment Variables, and remember that only NEXT_PUBLIC_ values reach the browser bundle.',
23 },
24];
25
26export function Multiple() {
27 return (
28 <Accordion type="multiple" className="w-full max-w-md">
29 {causes.map((cause) => (
30 <Accordion.Item key={cause.value} value={cause.value}>
31 <Accordion.Trigger value={cause.value}>{cause.trigger}</Accordion.Trigger>
32 <Accordion.Content value={cause.value} className="text-muted-foreground text-sm">
33 {cause.content}
34 </Accordion.Content>
35 </Accordion.Item>
36 ))}
37 </Accordion>
38 );
39}

Without default value

You can omit defaultValue to have the accordion start completely collapsed.
1'use client';
2
3import { Accordion } from '@/components/ui/accordion';
4
5const sections = [
6 {
7 value: 'authentication',
8 trigger: 'Authentication',
9 content:
10 'Send your secret key as a bearer token on every request. Keys are scoped per project, so a test key never touches live orders.',
11 },
12 {
13 value: 'rate-limits',
14 trigger: 'Rate limits',
15 content:
16 'The API allows 600 requests per minute per key. Every response carries the remaining budget in X-RateLimit-Remaining, and a 429 tells you how long to wait.',
17 },
18 {
19 value: 'webhooks',
20 trigger: 'Webhook retries',
21 content:
22 'A webhook that does not return a 2xx within 5 seconds is retried 8 times over 24 hours with exponential backoff. Reply fast and queue the work on your side.',
23 },
24 {
25 value: 'versioning',
26 trigger: 'Versioning',
27 content:
28 'Your account is pinned to the API version that was current when you signed up. Breaking changes ship under a new date version and never move you off yours.',
29 },
30];
31
32export function Collapsed() {
33 return (
34 <Accordion type="single" className="w-full max-w-md">
35 {sections.map((section) => (
36 <Accordion.Item key={section.value} value={section.value}>
37 <Accordion.Trigger value={section.value}>{section.trigger}</Accordion.Trigger>
38 <Accordion.Content value={section.value} className="text-muted-foreground text-sm">
39 {section.content}
40 </Accordion.Content>
41 </Accordion.Item>
42 ))}
43 </Accordion>
44 );
45}

API Reference

Accordion

PropTypeDefaultDescription
type"single" | "multiple""single"Controls whether only one item or multiple items can be open at the same time.
defaultValuestringundefinedThe initially opened item.
childrenReact.ReactNodeShould be AccordionItem components.
classNamestringAdditional classes for the wrapper.

Accordion.Item

PropTypeDefaultDescription
valuestringUnique identifier for the item. Required.
childrenReact.ReactNodeShould include AccordionTrigger and AccordionContent.
isOpenbooleanInternalDetermines whether the item is open.
onToggle() => voidInternalFunction to toggle open/closed state.
classNamestringAdditional classes for the wrapper.

Accordion.Trigger

PropTypeDefaultDescription
childrenReact.ReactNodeThe title or content of the button.
isOpenbooleanInternalWhether the item is open (controls icon rotation).
onToggle() => voidInternalClick handler for toggling.
classNamestringAdditional classes for the trigger button.

Accordion.Content

PropTypeDefaultDescription
childrenReact.ReactNodeCollapsible content.
isOpenbooleanInternalControls open state and animations.
classNamestringAdditional classes for the animated content wrapper.
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I