Accordion

A stack of collapsible panels.

Default

Each item carries data-state="open" or "closed". Multiple items may stay open at once. Click any header to toggle its panel.

A shared vocabulary of tokens, primitives, and patterns that lets every screen in a product look like it was made by the same hand. Stisla is one of them, implemented first as a vanilla CSS + JS layer, with React and Vue layers to follow.

The frame owns a single radius. Each item subtracts the wrapper padding from that radius so the inner arc shares a center with the outer arc. Change --accordion-radius on a parent and every nested item keeps the rhythm.

The chevron rotates today. The height transition lands with the Stisla.Accordion JS class in Step 4, same shape as the upcoming Sidebar and Dialog classes.
<div class="accordion" data-stisla-accordion>
  <div class="accordion__item" data-state="open">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="true" aria-controls="acc-default-1" id="acc-default-1-trigger">
        What is a design system?
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-default-1" role="region" aria-labelledby="acc-default-1-trigger">
      <div class="accordion__body-inner">
        A shared vocabulary of tokens, primitives, and patterns that lets every screen in a product look like it was made by the same hand. Stisla is one of them, implemented first as a vanilla CSS + JS layer, with React and Vue layers to follow.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-default-2" id="acc-default-2-trigger">
        How are corners kept concentric?
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-default-2" role="region" aria-labelledby="acc-default-2-trigger">
      <div class="accordion__body-inner">
        The frame owns a single radius. Each item subtracts the wrapper padding from that radius so the inner arc shares a center with the outer arc. Change <code>--accordion-radius</code> on a parent and every nested item keeps the rhythm.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-default-3" id="acc-default-3-trigger">
        Does it animate?
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-default-3" role="region" aria-labelledby="acc-default-3-trigger">
      <div class="accordion__body-inner">
        The chevron rotates today. The height transition lands with the Stisla.Accordion JS class in Step 4, same shape as the upcoming Sidebar and Dialog classes.
      </div>
    </div>
  </div>
</div>

Single open

Add data-stisla-accordion-type="single" on the root to enforce one-open-at-a-time. Opening any header closes the others.

Opening section two will close this panel.

Each header acts like a radio in a group.

Closing the current one without opening another is fine too. Just click an open header again.
<div class="accordion" data-stisla-accordion data-stisla-accordion-type="single">
  <div class="accordion__item" data-state="open">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="true" aria-controls="acc-single-1" id="acc-single-1-trigger">
        Section one
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-single-1" role="region" aria-labelledby="acc-single-1-trigger">
      <div class="accordion__body-inner">
        Opening section two will close this panel.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-single-2" id="acc-single-2-trigger">
        Section two
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-single-2" role="region" aria-labelledby="acc-single-2-trigger">
      <div class="accordion__body-inner">
        Each header acts like a radio in a group.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-single-3" id="acc-single-3-trigger">
        Section three
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-single-3" role="region" aria-labelledby="acc-single-3-trigger">
      <div class="accordion__body-inner">
        Closing the current one without opening another is fine too. Just click an open header again.
      </div>
    </div>
  </div>
</div>

Flush in a card

Add .accordion--flush to drop the outer frame so the accordion sits edge-to-edge inside a card or page. Items lose their chip inset and a single divider sits between rows.

Frequently asked

Point your domain at the build output. Any static host works.

Not in 3.0. The starter templates download as zips.

Pair @stisla/css with Radix or Base UI today. A first-party wrapper is post-3.0.
<div class="card">
  <div class="card__header">
    <h4 class="card__title">Frequently asked</h4>
  </div>
  <div class="accordion accordion--flush" data-stisla-accordion>
    <div class="accordion__item" data-state="open">
      <h4>
        <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="true" aria-controls="acc-flush-1" id="acc-flush-1-trigger">
          Can I deploy to my own domain?
          <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <polyline points="6 9 12 15 18 9"></polyline>
          </svg>
        </button>
      </h4>
      <div class="accordion__body" id="acc-flush-1" role="region" aria-labelledby="acc-flush-1-trigger">
        <div class="accordion__body-inner">
          Point your domain at the build output. Any static host works.
        </div>
      </div>
    </div>
    <div class="accordion__item" data-state="closed">
      <h4>
        <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-flush-2" id="acc-flush-2-trigger">
          Do you ship a CLI?
          <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <polyline points="6 9 12 15 18 9"></polyline>
          </svg>
        </button>
      </h4>
      <div class="accordion__body" id="acc-flush-2" role="region" aria-labelledby="acc-flush-2-trigger">
        <div class="accordion__body-inner">
          Not in 3.0. The starter templates download as zips.
        </div>
      </div>
    </div>
    <div class="accordion__item" data-state="closed">
      <h4>
        <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-flush-3" id="acc-flush-3-trigger">
          Is there a React wrapper?
          <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <polyline points="6 9 12 15 18 9"></polyline>
          </svg>
        </button>
      </h4>
      <div class="accordion__body" id="acc-flush-3" role="region" aria-labelledby="acc-flush-3-trigger">
        <div class="accordion__body-inner">
          Pair <code>@stisla/css</code> with Radix or Base UI today. A first-party wrapper is post-3.0.
        </div>
      </div>
    </div>
  </div>
</div>

With leading icon

Drop an icon inside the header before the label. The chevron stays pinned to the end.

Free standard shipping on orders over $50. Express options at checkout.

30-day window from delivery. Bring your order number.

A tracking link emails out once your package leaves the warehouse.
<div class="accordion" data-stisla-accordion>
  <div class="accordion__item" data-state="open">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="true" aria-controls="acc-icon-1" id="acc-icon-1-trigger">
        <span class="d-inline-flex align-items-center gap-2">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M12 2v6m0 0l3-3m-3 3l-3-3M5 12H2m20 0h-3M12 16v6m0-6l3 3m-3-3l-3 3"></path>
          </svg>
          Shipping
        </span>
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-icon-1" role="region" aria-labelledby="acc-icon-1-trigger">
      <div class="accordion__body-inner">
        Free standard shipping on orders over $50. Express options at checkout.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-icon-2" id="acc-icon-2-trigger">
        <span class="d-inline-flex align-items-center gap-2">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"></path>
          </svg>
          Returns
        </span>
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-icon-2" role="region" aria-labelledby="acc-icon-2-trigger">
      <div class="accordion__body-inner">
        30-day window from delivery. Bring your order number.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-icon-3" id="acc-icon-3-trigger">
        <span class="d-inline-flex align-items-center gap-2">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <circle cx="12" cy="12" r="10"></circle>
            <path d="M12 8v4l3 2"></path>
          </svg>
          Order tracking
        </span>
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-icon-3" role="region" aria-labelledby="acc-icon-3-trigger">
      <div class="accordion__body-inner">
        A tracking link emails out once your package leaves the warehouse.
      </div>
    </div>
  </div>
</div>

Disabled item

Mark a header disabled to keep it visible without a toggle target. The chevron stops responding and the row reads in the muted text color.

This one opens.

Body sits hidden; the header refuses interaction.
<div class="accordion" data-stisla-accordion>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-disabled-1" id="acc-disabled-1-trigger">
        Active section
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-disabled-1" role="region" aria-labelledby="acc-disabled-1-trigger">
      <div class="accordion__body-inner">
        This one opens.
      </div>
    </div>
  </div>
  <div class="accordion__item" data-state="closed">
    <h3>
      <button type="button" class="accordion__header" data-stisla-accordion-trigger aria-expanded="false" aria-controls="acc-disabled-2" id="acc-disabled-2-trigger" disabled>
        Locked section
        <svg class="accordion__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <polyline points="6 9 12 15 18 9"></polyline>
        </svg>
      </button>
    </h3>
    <div class="accordion__body" id="acc-disabled-2" role="region" aria-labelledby="acc-disabled-2-trigger">
      <div class="accordion__body-inner">
        Body sits hidden; the header refuses interaction.
      </div>
    </div>
  </div>
</div>

Customization

Override any variable on the element, on a parent scope, or on :root, and the cascade scopes the change. Geometry values multiply through var(--st-density), so the global density knob rescales the whole accordion.

Geometry

Variable Default Use
--accordion-radius var(--st-radius-lg) Outer frame radius. Large-surface tier, so it picks up --st-radius-lg (16 px under default density). The inner item radius derives concentrically. With the 4 px wrapper inset, the inner chip lands at 12 px, matching the standalone --st-radius default.
--accordion-padding 0.25rem Inset between frame edge and items. Drives the concentric inner radius.
--accordion-gap 0.25rem Space between adjacent items.

Frame surface

Variable Default Use
--accordion-bg var(--st-surface) Frame background.
--accordion-border-color var(--st-border) Frame border color.
--accordion-shadow var(--st-shadow) Ambient frame shadow. Drop to none for a flat frame.

Open-item chip

Variable Default Use
--accordion-item-open-bg var(--st-surface-2) Background of the chip wrapping an open item (header + body together).
--accordion-item-open-border-color var(--st-border) Border rim of the open-item chip.
Variable Default Use
--accordion-header-padding-y calc(0.75rem * var(--st-density)) Vertical padding inside the trigger row.
--accordion-header-padding-x calc(1rem * var(--st-density)) Horizontal padding inside the trigger row. Pairs with the 4 px frame inset for 20 px from frame edge to content, which aligns with card content padding.
--accordion-header-font-size inherit Trigger label font size.
--accordion-header-font-weight 500 Trigger label weight.
--accordion-header-color var(--st-foreground) Trigger label color. Stays the same in both states.
--accordion-header-bg transparent Trigger background at rest.
--accordion-header-hover-bg var(--st-accent) Trigger background under hover. Only paints when the item is closed; the open chip stays flat.
--accordion-header-divider-color var(--st-border) 1 px line between header and body when the item is open.

Icon

Variable Default Use
--accordion-icon-size 1rem Chevron width and height.
--accordion-icon-color currentColor Chevron stroke. Tracks the header color by default.
--accordion-icon-transition-duration 0.15s Rotation duration when an item opens or closes.

Body

Variable Default Use
--accordion-body-padding-y calc(0.75rem * var(--st-density)) Vertical padding inside the body region.
--accordion-body-padding-x calc(1rem * var(--st-density)) Horizontal padding inside the body region. Matches header padding so the body content lines up flush with the trigger label.
--accordion-body-color var(--st-foreground) Body text color.
--accordion-body-transition-duration 0.2s Reserved for the Step 4 height transition. No-op today.

Focus

Variable Default Use
--accordion-ring var(--st-ring) Keyboard focus outline color on the header.