Skip to content

Accordion

A group of Collapse items with one shared name. Opening one closes the others and no JavaScript runs to make that happen — <details name> is the whole mechanism, and the component's only job is to hand every item the same name and sew the boxes into a single card.

  • Stable
  • React 19
  • Zero JS
  • Native exclusivity

General

Language, region and the login items.

Appearance

Themes, accent colour and text size.

Privacy

What each app is allowed to reach.

General

Language, region and the login items.

Appearance

Themes, accent colour and text size.

Privacy

What each app is allowed to reach.
Code
<Accordion>
<Collapse headingLevel={3} title="General">
Language, region and the login items.
</Collapse>
<Collapse headingLevel={3} title="Appearance">
Themes, accent colour and text size.
</Collapse>
<Collapse headingLevel={3} title="Privacy">
What each app is allowed to reach.
</Collapse>
</Accordion>
<div class="wayra-accordion">
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings" open>
<summary><h3>General</h3></summary>
<div class="wayra-collapse-content">Language, region and the login items.</div>
</details>
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings">
<summary><h3>Appearance</h3></summary>
<div class="wayra-collapse-content">Themes, accent colour and text size.</div>
</details>
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings">
<summary><h3>Privacy</h3></summary>
<div class="wayra-collapse-content">What each app is allowed to reach.</div>
</details>
</div>

The children are ordinary Collapse elements. The group writes a name onto each of them — generated with useId unless you pass one — and the browser does the rest: a <details> that opens closes any sibling sharing its name.

Only Collapse children are touched, and an item that brought its own name keeps it. Anything else in the group passes through untouched.

General

Language, region and the login items.

Appearance

Themes, accent colour and text size.

Privacy

What each app is allowed to reach.

Interior corners squared, one shared hairline, the box radius only at the two ends.

Code
<Accordion>{items}</Accordion>
<div class="wayra-accordion">
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings" open>
<summary><h3>General</h3></summary>
<div class="wayra-collapse-content">Language, region and the login items.</div>
</details>
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings">
<summary><h3>Appearance</h3></summary>
<div class="wayra-collapse-content">Themes, accent colour and text size.</div>
</details>
<details class="wayra-collapse" data-variant="boxed" data-marker="chevron" name="settings">
<summary><h3>Privacy</h3></summary>
<div class="wayra-collapse-content">What each app is allowed to reach.</div>
</details>
</div>

Boxed items inside a group lose their interior radii, the item after a neighbour drops its top border, and only the first and last keep a rounded corner. Every rule keys on .wayra-accordion >, so a Collapse on its own is never affected — and because those selectors score higher than the component’s own, the seam does not depend on import order to win.

General

Language, region and the login items.

Appearance

Themes, accent colour and text size.
Code
<Accordion exclusive={false}>
<Collapse marker="plus" headingLevel={3} title="General">…</Collapse>
<Collapse marker="plus" headingLevel={3} title="Appearance">…</Collapse>
</Accordion>
<div class="wayra-accordion">
<details class="wayra-collapse" data-variant="boxed" data-marker="plus" open>
<summary><h3>General</h3></summary>
<div class="wayra-collapse-content">Language, region and the login items.</div>
</details>
<details class="wayra-collapse" data-variant="boxed" data-marker="plus" open>
<summary><h3>Appearance</h3></summary>
<div class="wayra-collapse-content">Themes, accent colour and text size.</div>
</details>
</div>

exclusive={false} writes no name, so the items stop knowing about each other and any number of them can stay open. Note the DOM: there is no attribute to remove afterwards, because there was never one to add.

General
Language, region and the login items.
Appearance
Themes, accent colour and text size.
Code
<Accordion>
<Collapse variant="plain" title="General">…</Collapse>
<Collapse variant="plain" title="Appearance">…</Collapse>
</Accordion>
<div class="wayra-accordion">
<details class="wayra-collapse" data-variant="plain" data-marker="chevron" name="plain-demo">
<summary>General</summary>
<div class="wayra-collapse-content">Language, region and the login items.</div>
</details>
<details class="wayra-collapse" data-variant="plain" data-marker="chevron" name="plain-demo">
<summary>Appearance</summary>
<div class="wayra-collapse-content">Themes, accent colour and text size.</div>
</details>
</div>

A plain item draws no box, so the group supplies the separator — and it does it by colouring the transparent border the item already reserves, which is why nothing shifts by a pixel when you switch variants.

The class is the API, and the exclusivity is an attribute rather than a behaviour, so hand-written markup is not a downgrade: write <div class="wayra-accordion"> around your <details name="…"> elements and you have the same component. Every html panel on this page is exactly that.

unstyled drops the group class and leaves the children alone — each Collapse opts out on its own.

AccordionProps extends ComponentProps<"div">.

Prop Type Default Description
exclusive boolean true Opening one item closes the others.
name string useId() The shared group name written onto each item.
children ReactNode — Collapse elements.
unstyled boolean false Drops the group class.

Pass name explicitly when two groups must share one exclusive set, or when the markup is server-rendered somewhere that needs a stable, readable name.

  • Give every item a headingLevel. The accordion pattern is a list of headings, each toggling its own section; without them a screen reader user can still tab through, but loses heading navigation. The heading goes inside the <summary>, which is where the content model allows it.
  • No roving tabindex, no aria-expanded, no key handlers. Each item is a <summary>, so the browser announces its own expanded state and Tab moves between items. Adding ARIA on top would repeat — and eventually contradict — what the element already says.
  • The group itself is a plain <div> with no role. It is a visual seam, not a widget: the semantics live in the items.