GitHub repository

Accordion

Collapsible sections with one or many open at a time.

A vertically stacked set of collapsible sections. Each item is a heading-wrapped `<button>` toggle (label + optional hint + a chevron that rotates 180° on open) over an animated content `region`. Single-open by default; `multiple` lets several stay open. Works uncontrolled (`defaultValue`) or controlled (bindable `value` + `onChange`). The reveal uses the `--ss-dur` / `--ss-ease` motion tokens and collapses instantly under `prefers-reduced-motion`.

Demo

live · storybook Open in Storybook

Demo loads from https://dssoca-storybook.vercel.app/. Run pnpm storybook for it to render locally.

Usage

svelte
<script>
  import { Accordion } from 'dssoca';

  const items = [
    { id: 'shipping', label: 'Shipping' },
    { id: 'returns', label: 'Returns', hint: '30 days' },
  ];
</script>

<Accordion {items} defaultValue="shipping">
  {#snippet panel(item)}
    <p>Content for {item.label}.</p>
  {/snippet}
</Accordion>

Each header is a real `<button>` with `aria-expanded` + `aria-controls`; the panel is a `role="region"` labelled by its header (`aria-labelledby`). Enter/Space toggle; ArrowUp/ArrowDown move between headers (wrapping), Home/End jump to the first/last. Disabled sections use `aria-disabled` so they stay reachable by keyboard but do not toggle. The toggle chevron is the shared `Icon` `chevron` glyph (DS-0110) — `aria-hidden`, inside the header button — rotating 180° on open; the Accordion resolves its size once and passes it explicitly to that Icon (DS-0111), so a sm/md/lg Accordion gets a matching chevron, text, and spacing. The panel content is laid out as an always-padded block inside the grid-rows reveal clip, so it animates open as a stable padded block with no mid-animation reflow (DS-0115). Use `overflow="truncate"` to keep a long header label on a single line with an ellipsis (default `wrap`); a custom `header` snippet owns its own overflow handling. Zero border-radius; padding/typography scale with the size axis. WCAG 2.2 AA.

HTML

interactive with vanilla.js The exact markup the component renders, generated at build time. Use it with dssoca/vanilla.css+ dssoca/vanilla.js — see Plain HTML & CSS.

html
<div class="ss-accordion" data-overflow="wrap">
  <div class="item">
    <div role="heading" aria-level="3" class="heading">
      <button type="button" class="head" id="acc-h-a" aria-expanded="false" aria-controls="acc-p-a">
        <span class="title">Alpha</span>
        <span class="hint">first</span>
        <span class="chevron" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" class="ss-icon" style="width:var(--ss-icon);height:var(--ss-icon)" aria-hidden="true" focusable="false">
            <path d="M8 10l4 4 4-4"/>
          </svg>
        </span>
      </button>
    </div>
    <div class="panel" id="acc-p-a" role="region" aria-labelledby="acc-h-a" hidden="">
      <div class="panel-clip">
        <div class="panel-inner">
          <p>Panel content.</p>
        </div>
      </div>
    </div>
  </div>
  <div class="item">
    <div role="heading" aria-level="3" class="heading">
      <button type="button" class="head" id="acc-h-b" aria-expanded="false" aria-controls="acc-p-b">
        <span class="title">Beta</span>
        <span class="chevron" aria-hidden="true">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" class="ss-icon" style="width:var(--ss-icon);height:var(--ss-icon)" aria-hidden="true" focusable="false">
            <path d="M8 10l4 4 4-4"/>
          </svg>
        </span>
      </button>
    </div>
    <div class="panel" id="acc-p-b" role="region" aria-labelledby="acc-h-b" hidden="">
      <div class="panel-clip">
        <div class="panel-inner">
          <p>Panel content.</p>
        </div>
      </div>
    </div>
  </div>
</div>

vanilla.js toggles the panel referenced by each header's aria-controls (single-open; add data-ss-multiple on the root to allow several) and roves focus with Arrow/Home/End. Emits ss:change.

Props

PropTypeDefaultDescription
itemsAccordionItem[]—Required. Sections — each `{ id, label, hint?, disabled? }`.
panelSnippet<[AccordionItem]>—Required. Renders the content for each section; receives the item.
headerSnippet<[AccordionItem]>—Optional custom header content (replaces the default label + hint).
multiplebooleanfalseAllow several sections open at once; otherwise opening one closes the others.
valuestring | string[]—Controlled, bindable open state — an id (single) or list of ids (`multiple`).
defaultValuestring | string[]—Initial open section(s) when uncontrolled.
onChange(value) => void—Fired with the new open value whenever a section toggles.
overflow'truncate' | 'wrap''wrap'How a long header label behaves when it exceeds the available width. `wrap` (default) flows onto multiple lines; `truncate` keeps it on a single line with an ellipsis. Applies to the default header markup only.
headingLevel1 | 2 | 3 | 4 | 5 | 63Heading level wrapping each header button (document outline).
idBasestring—Base id used to namespace the generated header/panel ids. Defaults to a stable per-instance id (from `$props.id()`) so server- and client-rendered IDs match (SSR-safe).
size'sm' | 'md' | 'lg'—Per-instance size override; inherits the ancestor `data-size-variant` when unset.
↑↓ navigate · ↵ open · esc close

Keyboard shortcuts

No shortcuts registered

Shortcuts added through the dssoca registry will show up here.

Single-key shortcuts