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.
--accordion-radius on a parent and every nested item keeps the rhythm.
<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.
<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
@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.
<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.
<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. |
Header
| 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. |