/*
* Accordion — block
*
* Built on native <details>/<summary> for keyboard, focus, and screen
* reader support out of the box. CSS-only — no runtime, no JavaScript.
*
* Per ADR-0007 + answered design question: each <details> is
* independent (no single-open coordination). If an author wants
* single-open behavior, they add their own JS.
*
* <section class="accordion">
* <details class="accordion__item">
* <summary class="accordion__summary">Section 1</summary>
* <div class="accordion__body">…</div>
* </details>
* <details class="accordion__item">
* <summary class="accordion__summary">Section 2</summary>
* <div class="accordion__body">…</div>
* </details>
* </section>
*/
@layer malevich.components {
.accordion {
display: flex;
flex-direction: column;
border: var(--border-width-hairline) solid var(--color-border-muted);
border-radius: var(--radius-card);
background-color: var(--color-surface-raised);
overflow: hidden;
}
.accordion__item {
border-block-end: var(--border-width-hairline) solid var(--color-border-muted);
}
.accordion__item:last-child {
border-block-end: 0;
}
.accordion__summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-gap-elements-m);
padding-block: var(--space-inset-block-m);
padding-inline: var(--space-inset-block-m);
color: var(--color-ink-strong);
font-family: var(--font-heading-subsection-family);
font-size: var(--font-heading-subsection-size);
font-weight: var(--font-heading-subsection-weight);
line-height: var(--font-heading-subsection-line-height);
letter-spacing: var(--font-heading-subsection-letter-spacing);
transition: background-color var(--motion-fast) var(--motion-easing-default);
}
/* Hide the native disclosure triangle across browsers */
.accordion__summary::-webkit-details-marker {
display: none;
}
.accordion__summary::marker {
content: "";
}
.accordion__summary:hover {
background-color: var(--color-surface-canvas);
}
.accordion__summary:focus-visible {
outline: var(--border-width-focus) solid var(--color-accent);
outline-offset: calc(-1 * var(--border-width-focus));
}
/* Custom chevron indicator. Rotates when the parent <details> is open. */
.accordion__summary::after {
content: "";
flex-shrink: 0;
inline-size: var(--space-inset-element-m);
block-size: var(--space-inset-element-m);
background-color: currentColor;
/* SVG-as-mask: chevron pointing down */
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 6 L8 10 L12 6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4 6 L8 10 L12 6' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
mask-size: contain;
-webkit-mask-size: contain;
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-position: center;
-webkit-mask-position: center;
transition: transform var(--motion-fast) var(--motion-easing-default);
}
.accordion__item[open] > .accordion__summary::after {
transform: rotate(180deg);
}
.accordion__body {
padding-block-start: 0;
padding-block-end: var(--space-inset-block-m);
padding-inline: var(--space-inset-block-m);
color: var(--color-ink-regular);
font-family: var(--font-body-regular-family);
font-size: var(--font-body-regular-size);
font-weight: var(--font-body-regular-weight);
line-height: var(--font-body-regular-line-height);
letter-spacing: var(--font-body-regular-letter-spacing);
}
/* Variants */
.accordion.-flush {
border: 0;
border-radius: 0;
background-color: transparent;
}
.accordion.-flush .accordion__item {
border-block-end: var(--border-width-hairline) solid var(--color-border-muted);
}
.accordion.-flush .accordion__item:last-child {
border-block-end: 0;
}
@media (prefers-reduced-motion: reduce) {
.accordion__summary,
.accordion__summary::after {
transition: none;
}
}
}
This component is pure CSS — no JavaScript required.
# Accordion — AGENTS.md
> Auto-generated from `accordion.docs.md` + the modifier applicability matrix.
> Edit those source files; this file regenerates on build.
## Identity
- **Component:** `accordion`
- **Layer:** blocks
- **Status:** stable
- **Last updated:** 2026-05-19
## Summary
Use an accordion to let users disclose related content on demand:
## Variants
| Variant | Class | Use for |
|---------|-----------------------|----------------------------------------|
| Default | `.accordion` | Card-style group with border and bg |
| Flush | `.accordion.-flush` | No outer border, item separators only |
## Modifier applicability
Per ADR-0007. Modifiers attach via `data-{category}="value"` attributes.
| Category | Accepts |
|---|---|
| `background` | — |
| `surface` | `flat`, `elevated` |
| `effect` | `ring` |
| `shader` | — |
| `rhythm` | `compact`, `regular`, `spacious` |
| `motion` | any value |
| `behavior` | `collapsible` |
## Anatomy
```html
<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 consumed
### 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 contract
Native `<details>`/`<summary>` carries all required semantics:
- The disclosure widget is keyboard-operable (Enter/Space toggle).
- Screen readers announce expanded/collapsed state via the `open`
attribute.
- 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`.
## Guidance
### 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 `open` if 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.