Accordion
Overview
Single or multiple disclosure groups with roving focus.
Pattern: Accordion. Status: stable.
When to use
- Grouped disclosure panels with single or multiple expansion and roving keyboard navigation.
- Dynamic sections where registration cleanup must keep focus and expanded state valid.
When not to use
- Independent one-off toggles; use Disclosure or Collapsible.
- Tabs, where only one panel should be selected and announced as a tabpanel.
Import
ts
import { createAccordion } from 'ui-headless-runtime';Controller creation
Create Accordion during component mount or setup, subscribe before rendering derived UI, and keep every cleanup returned by registrations or DOM binding.
Options
- Public options:
collapsible,id,loop,type,defaultValue,getValue,onValueChange,subscribeValue. typeissingleormultiple;collapsibleaffects whether a single accordion may close its current item. Disabled state and custom trigger/panel IDs belong to each registeredAccordionItem.
Snapshot
- Snapshot fields:
controlled,expandedIds,focusedId,items,type. - Each item snapshot contains
id,triggerId,panelId,expanded,disabled, andtabIndex.
Commands
- Component commands:
focus,handleTriggerKeyDown,registerItem,toggle. - Every controller also provides
getSnapshot,subscribe, anddestroythroughRuntimeController.
Events
- Events:
beforeChange,stateChange. - Both payloads contain
itemId, the requestedexpandedIds, and typed change details;beforeChangeis cancellable.
Change reasons
- Change reasons:
programmatic,trigger,keyboard.
Controlled mode
Controlled accordions report the requested readonly string[] of expanded item IDs and wait for the consumer store to commit it.
Uncontrolled mode
Uncontrolled accordions own the expanded set and active trigger.
DOM binding
- Register each trigger/panel pair with stable IDs and release that registration when the pair unmounts.
Required markup
- Place triggers inside meaningful headings and connect each trigger to its panel.
ARIA contract
- Apply
aria-expanded,aria-controls, trigger IDs, and panel labelling from the item snapshot.
Keyboard interaction
- ArrowDown / ArrowUp: Move focus between enabled triggers.
- Home / End: Move focus to the first or last enabled trigger.
- Enter / Space: Toggle the focused section.
Focus behavior
- Arrow keys, Home, and End move the active trigger while skipping disabled items.
Nested behavior
- Nested accordions should use independent controller instances and IDs.
Cleanup
- Unregistering the active item selects a valid neighbor; destroy releases all item registrations.
Minimal lifecycle example
ts
import { createAccordion } from 'ui-headless-runtime';
const controller = createAccordion();
const unsubscribe = controller.subscribe((snapshot) => {
console.log(snapshot);
});
console.log(controller.getSnapshot());
unsubscribe();
controller.destroy();The production demo loads the exact executable module from apps/demo/src/examples/accordion.ts.
Edge cases
single: At most one section is expanded.multiple: Several sections may remain expanded.dynamic: Registration cleanup preserves valid focus.
Limitations
- Heading level and content hierarchy are consumer responsibilities.
Related links
API reference
See createAccordion.