Accordion

Default

What is Malevich?
A semantic design system rooted in suprematism, built for humans and AI agents.
Why semantic-first?
Standard HTML elements carry meaning that custom elements do not. Native semantics work across browsers and assistive technology without component-specific shims.
Can I use it in React?
Malevich is framework-agnostic — standard HTML + CSS. It works in React, Vue, Svelte, or vanilla HTML.

Flush

Item one
Body text for item one.
Item two
Body text for item two.
Item three
Body text for item three.

  
/*
 * 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.