accordion()
A styled, accessible disclosure and accordion built on the native
<details> / <summary> elements. The presentation lives entirely
in ui.css and works with zero JavaScript — hand-write a
<details class="ui-disclosure">, or an exclusive group of
<details name="…"> inside <div class="ui-accordion">, and it
provides the shared appearance and native disclosure behavior.
The accordion() decorator (/ui/lib/accordion.mjs) is a progressive
enhancement — like tree(), you own the markup and it
automates the repetitive markup: wrapping each
panel in a labelled role="region", generating the aria-labelledby /
aria-controls wiring, exclusive open via the native name attribute, and
lazy load(). Demo 1 writes this markup by hand; demo 2
produces the identical result from minimal markup with the decorator.
The decorator demos put their source in a <template> and render it live with
<ui-code for="…-tpl" into="…">: because accordion() rewrites the live
DOM (wrapping panels, adding ARIA), a <template> keeps the shown markup
pristine — see ui-code §Picking a target.
<summary> has an implicit role="button" and conveys the
open/closed state to assistive tech (screen readers announce "expanded/collapsed"), so
no aria-expanded is added — it could conflict with the native state
(WCAG SC 4.1.2 — Name, Role, Value).role="region" labelled by their summary via
aria-labelledby; the summary gets aria-controls → the panel.role="region" defaults on for a group, off for a
single disclosure. The APG cautions against marking panels as regions past ~6 panels (landmark
proliferation) — pass { region: false } for long lists.name attribute (one panel open at a time); no JS
state machine, so it survives "Find in page" and deep links.aria-busy on the
summary while fetching, and a failed load closes the panel again so it can be retried.::before (invisible to AT); the open animation is a
progressive enhancement disabled under prefers-reduced-motion.| Export | Signature | Description |
|---|---|---|
accordion |
accordion(root, { load?, exclusive?, name?, region?, label?, loading_label?, error_label? }) |
Enhances a group container (its > details children) or a single <details>. Returns a handle. |
load(details) → panel content for a [data-lazy] node (HTML string, a
<ul>/<ol> to unwrap, or nodes; may be async; rejection reverts
the open) ·
exclusive sets a shared native name ·
region toggles role="region" on panels ·
Handle: items(), open(id?), close(id?),
toggle(id?), refresh(), destroy() (address items by
data-id) ·
Mark decorative summary content (icons, badges) aria-hidden="true" to exclude it from the button's accessible name.
A single styled <details class="ui-disclosure">. No decorator, no script — just
CSS. Author the disclosed content in a .ui-disclosure-panel for the right padding.
A check that determines whether a document is ready to publish.
This example writes the complete accessible markup: a shared name, panels with role="region" labelled by their summaries, and aria-controls links from summaries to panels. The decorator in demo 2 produces the same structure.
Open Checks in the toolbar and click "Run check".
A blocking check an owner has deliberately approved with a comment.
accordion()The same result from minimal markup: plain <details><summary> with the
content inline. The decorator adds .ui-accordion, wraps each panel in a labelled
role="region", wires aria-labelledby/aria-controls, and sets
the shared name for { exclusive: true }. Inspect the rendered widget to
see the injected ARIA.
Open Checks in the toolbar and click "Run check".
A blocking check an owner has deliberately approved with a comment.
Mark a node [data-lazy] and pass { load }; the loader runs the first
time the panel opens. While loading the summary is aria-busy and a polite live
region announces progress; a rejection closes the panel and keeps it lazy so it can be retried.
The summary is a flex row: a leading status <ui-icon> (decorative,
aria-hidden), the label, and a .ui-disclosure-aside cluster pinned to the
end for a summary line and a .badge. This is the shape the delivery checklist uses
for its check rows.
Every sentence is recorded.
Chapters 3 and 5 fall outside −18 LUFS ±1.
tree() — the sibling decorator for nested navigation trees.<ui-code> — the for/into live-source pattern used above.