Prototype Library

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.

Inherited .astro import EsaCollapsible from '@esa/ecology/esa-collapsible.astro';

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

PropTypeDefaultDescription
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

SlotTypeDefaultDescription
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-written aria-expanded on 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 ::after and 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.
  • title is 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.