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.

Accessibility (WCAG)

ExportSignatureDescription
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.

0. Disclosure — zero JavaScript

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.

Show code

1. Accordion with explicit markup

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.

Show code

2. Accordion — via 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.

Show code

3. Lazy panels

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.

Show code

4. Rich summary — delivery-checklist row

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.

Show code

See also