Collapsible
A single disclosure section — icon + title over a slotted body — built on native <details>/<summary>. Free a11y, keyboard, and open/close state, with no JavaScript.
Preview
Permit conditions
Body content, revealed on toggle. The whole summary row is the control — not just the caret.
<Collapsible title="Permit conditions">
<p>Body content, revealed on toggle.</p>
</Collapsible> Icon and default state
A leading icon reads as a quiet, secondary glyph — it labels the section, it doesn't decorate it.
open renders the section expanded on load.
Site details
Open on load — use this for the section a reader wants first.
Attachments
Closed by default.
<Collapsible title="Site details" icon="map-pin" open>…</Collapsible>
<Collapsible title="Attachments" icon="file-text">…</Collapsible> Flush
flush strips the card chrome and the horizontal padding, so sections sit directly
in a drawer, sidebar, or filter rail where the host already supplies the dividers and gutter.
Status
Flush sections take the host's gutter, not their own.
Date range
Stacked, each opening independently.
<Collapsible title="Status" icon="circle-check" flush open>…</Collapsible>
<Collapsible title="Date range" icon="calendar" flush>…</Collapsible> Stacking — not an accordion
This is a single disclosure, not a single-open accordion group. Stack several
for a set and each opens independently, matching native <details> semantics.
There is deliberately no group component: "only one open at a time" is a behavior that costs
readers the comparison they usually want, and it takes JS to enforce something the platform
gives you for free otherwise.
Overview
Both of these can be open at once.
Conditions
Independent state per section.
History
No coordination between siblings.
<Collapsible title="Overview" icon="info" open>…</Collapsible>
<Collapsible title="Conditions" icon="notepad-text">…</Collapsible>
<Collapsible title="History" icon="history">…</Collapsible> API
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | required | Summary label. Required — the row is the control, so it always needs a name. |
icon | string | — | Optional leading Lucide icon name (kebab-case), rendered through esa-icon at size sm. |
iconPaths | string | — | Inner SVG markup for an icon outside esa-icon's registry. Passed straight through. |
open | boolean | false | Render expanded on load. |
flush | boolean | false | Drop the card chrome (border, radius, background) and the horizontal padding — for stacked drawer or sidebar sections separated by the host’s own dividers. |
Slots
| Slot | Type | Default | Description |
|---|---|---|---|
default | slot | — | The disclosed body. A flex column with a 1rem gap between children. |
Accessibility
- Native
<details>/<summary>: the expanded state, the button role, Enter/Space activation, and focus all come from the platform. No ARIA is added, and none should be — hand-writtenaria-expandedon a summary fights the element. - The entire summary row is the hit target, not just the caret — a full-width tap target, which is the point of using the native element.
- The caret is a CSS
::afterand the native marker is hidden, so nothing is announced twice. - Collapsed content is genuinely hidden from find-in-page in some browsers. Don't hide content a reader must be able to search for — and never a form field carrying a validation error.
titleis a<span>, not a heading. If the sections are page structure rather than a widget, wrap each summary's label in the right<h*>at the call site.