Accordion
A vertically-stacked set of collapsible sections. Built on native
<details>/<summary> for keyboard, focus, and screen reader
behavior out of the box. CSS-only — no JavaScript, no runtime.
When to use
Use an accordion to let users disclose related content on demand: FAQs, documentation sections, settings groups. Each section is independent — opening one does not close another. This matches the HTML standard and avoids the "where did my content go?" surprise that single-open accordions can cause when scroll position shifts.
If you need single-open behavior, add a small piece of JavaScript that
listens to toggle events on the items and closes siblings. The
component does not ship this behavior because most patterns do not
need it and the surprise factor is real.
Variants
| Variant | Class | Use for |
|---|---|---|
| Default | .accordion |
Card-style group with border and bg |
| Flush | .accordion.-flush |
No outer border, item separators only |
Anatomy
<section class="accordion">
<details class="accordion__item">
<summary class="accordion__summary">What is Malevich?</summary>
<div class="accordion__body">
A semantic design system rooted in suprematism, built for humans
and AI agents.
</div>
</details>
<details class="accordion__item" open>
<summary class="accordion__summary">Why semantic-first?</summary>
<div class="accordion__body">
Standard HTML elements carry meaning that custom elements do not.
Native semantics work across browsers and assistive technology
without component-specific shims.
</div>
</details>
</section>
Add open on <details> to render expanded by default.
Tokens used
From semantic tier
--color-surface-raised— container background--color-surface-canvas— summary hover--color-ink-strong— summary text--color-ink-regular— body text--color-border-muted— borders--color-accent— focus ring--space-inset-block-m— internal padding--space-inset-element-m— chevron size--space-gap-elements-m— gap between label and chevron--radius-card— container radius--border-width-hairline/--border-width-focus--font-heading-subsection-*— summary typography--font-body-regular-*— body typography--motion-fast/--motion-easing-default— chevron rotation
Accessibility
Native <details>/<summary> carries all required semantics:
- The disclosure widget is keyboard-operable (Enter/Space toggle).
- Screen readers announce expanded/collapsed state via the
openattribute. - Focus management requires no JS;
<summary>is the natural focusable element. - The chevron is decorative (purely visual via a CSS mask) and does not pollute the accessibility tree.
The native disclosure marker is removed via list-style: none and
the ::-webkit-details-marker and ::marker pseudo-elements. A
custom chevron renders via a CSS mask and rotates 180° when the item
is open. The rotation honors prefers-reduced-motion.
Edge cases
- Nested accordions: allowed. Each
<details>is independent. Avoid deeper than two levels — it stops being navigable. - Forms inside accordion bodies: native
<details>does not capture focus on toggle, so a closed accordion containing a form field that gains focus (e.g. viaautofocus) will open automatically. This is correct behavior — open the section containing focused content. - Long titles: summary uses
display: flexwith a chevron pushed to the inline-end; the title takes the remaining width and wraps if needed.
Do
- Use
<details>and<summary>as the tags. They carry the semantics this component depends on. - Wrap the body in a
<div class="accordion__body">so padding applies even if the author writes raw text. - Mark the most likely-visible item with
openif there is a sensible default to show.
Don't
- Don't replace
<details>with<div>. The component leans on native semantics for keyboard and screen reader support. - Don't add single-open coordination unless the design genuinely requires it; even then, write a small inline script — don't bake it into the component.
- Don't animate the body's height with JS to "improve" the open
transition. Native
<details>doesn't animate height by default and that is fine.