Accordion
When to use
Section titled “When to use”- A FAQ, where each question opens its answer
- Long settings or order details split into sections people open as needed
- Content that should be findable with the browser’s find-in-page while closed
Not when people need to compare sections side by side, or every section is short — show them open instead.
Choosing between organising content:
| Component | Use it for | For example |
|---|---|---|
| Accordion | Sections stacked on one page, of which people read one or a few | FAQ, settings groups, order details |
| Tabs | Views of the same thing at the same level, one at a time | Account / Billing / Security |
| Card | A self-contained group of content, shown as one unit, often in a grid or list | A product tile, a project summary |
| Table | Rows of records compared across the same columns | Invoices, users, order lines |
| Separator | A line that divides sections, with no content of its own | Between menu groups or toolbar groups |
How long does shipping take?
Two to five working days, tracked.
Can I return an order?
Within thirty days, unused, with the receipt.
Do you ship abroad?
To most of Europe and North America.
import { Accordion } from '@arun-dev/ui';
const faq = [ { q: 'How long does shipping take?', a: 'Two to five working days, tracked.' }, { q: 'Can I return an order?', a: 'Within thirty days, unused, with the receipt.' }, { q: 'Do you ship abroad?', a: 'To most of Europe and North America.' },];
// exclusive: opening one closes the others — the browser does it, through <details name>.export default function AccordionBasics() { return ( <Accordion.Root exclusive style={{ inlineSize: '28rem', maxInlineSize: '100%' }}> {faq.map(({ q, a }, index) => ( <Accordion.Item key={q} defaultOpen={index === 0}> <Accordion.Trigger>{q}</Accordion.Trigger> <Accordion.Panel>{a}</Accordion.Panel> </Accordion.Item> ))} </Accordion.Root> );}Accordion is built from native <details> and <summary>. The browser does the work:
- A press, Enter or Space toggles an item.
- Items with the same
nameopen one at a time.exclusiveon the Root gives them one. - In Chromium, find-in-page opens a closed item when the match is inside it.
- Opening animates where the browser supports it (
::details-content), and snaps elsewhere and under reduced motion.
Anatomy
Section titled “Anatomy”import { Accordion } from '@arun-dev/ui';
<Accordion.Root exclusive> <Accordion.Item defaultOpen> <Accordion.Trigger>Shipping</Accordion.Trigger> <Accordion.Panel>Two to five working days.</Accordion.Panel> </Accordion.Item></Accordion.Root>;| Part | Renders | Does |
|---|---|---|
Root | <div> | optional; exclusive makes its Items open one at a time |
Item | <details> | one section; works alone as a single disclosure |
Trigger | <summary> | the always-visible line, with a chevron |
Panel | <div> | what the Item reveals |
Controlled
Section titled “Controlled”defaultOpen sets the starting state. To control it, pass open and update it from
onToggle:
<Accordion.Item open={open} onToggle={(event) => setOpen(event.newState === 'open')}>Playground
Section titled “Playground”Shipping
Everything about shipping.
Returns
Everything about returns.
Warranty
Everything about warranty.
Open two items: with exclusive the first closes as the second opens; without it, both stay open.
<Accordion.Root exclusive><Accordion.Item /> …</Accordion.Root>Accordion.Root
Section titled “Accordion.Root”| Prop | Type | Default | Description |
|---|---|---|---|
exclusive | boolean | false | Only one Item open at a time: every Item gets the same generated name. |
className | string | Concatenated with the component's own classes, never replacing them. | |
render | ReactElement | Element or component to render instead of the default. Props, className, event handlers and ref are merged onto it. | |
ref | Ref<HTMLElement> | Ref to the rendered element. Merged with any ref on the render element. | |
…rest | HTMLAttributes | Anything else is spread onto the rendered element, so id, aria-*, data-* and event handlers all reach the DOM. |
Accordion.Item
Section titled “Accordion.Item”| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | Open at mount. Read once; the browser owns it after that. |
open | boolean | Controlled state. Pair with onToggle, whose event has newState: "open" or "closed". | |
name | string | Items with the same name open one at a time, natively. Overrides the Root's. |
<summary>is a button to screen readers, so a heading inside it isn’t reached by heading navigation. That’s the price of the native element. If the sections need headings, put one above the accordion.- Restyle with the
--accordion-*tokens.