@19h47/accordion
v9.1.0
Published
Accordion
Readme
@19h47/accordion
Sur un petit air d'accordéon Léon
Accessible accordion for the modern web. Panels are resolved through ariaControlsElements (Baseline 2025), with keyboard support aligned to the WAI-ARIA Accordion pattern.
Requirements
- ESM-capable environment
- Browsers with
Element.ariaControlsElementssupport
Installation
pnpm add @19h47/accordionAlso works with npm / yarn / bun.
Usage
import Accordion from '@19h47/accordion';
const element = document.querySelector('.js-accordion');
const accordion = new Accordion(element);
accordion.init();<div class="js-accordion">
<div
class="js-accordion-panel"
data-accordion-open="true"
data-accordion-deselect="true"
>
<h3>
<button
class="js-accordion-header"
type="button"
id="lorem-header"
aria-expanded="true"
aria-controls="lorem-body"
>
Toggle
</button>
</h3>
<div id="lorem-body" role="region" aria-labelledby="lorem-header">
<div class="js-accordion-inner">
Panel content
</div>
</div>
</div>
</div>The header keeps an aria-controls IDREF in markup. At runtime, the library resolves the panel via ariaControlsElements.
Wrap each header control in a heading (<h2>–<h6>, or nested deeper levels as needed). The library does not inject or enforce this — clean document outline is up to you.
Header: <button> vs <a>
Both work as .js-accordion-header. Choose based on intent:
| | <button type="button"> | <a href="#panel-id"> |
| --- | --- | --- |
| Role | Toggle control (APG default) | Link + deep link to the panel |
| Activation | Click, Enter, Space | Click / Enter (native link) |
| URL hash | Optional, via your own logic | Native (href + browser history) |
| Best for | In-page accordion only | Shareable / bookmarkable panels |
Use <button> when the header only expands or collapses. Use <a href="#…"> when the panel id should also be a real document fragment.
For deep links, point href at the panel root (.js-accordion-panel, always in the layout), not the collapsible region. A fragment on a hidden / zero-height body makes the browser scroll to the wrong place.
Markup checklist
| Selector / attribute | Role |
| --- | --- |
| .js-accordion | Root container |
| .js-accordion-panel | One panel (direct child of the root) |
| heading (h2–h6) | Wraps the header control (consumer markup; not enforced by the library) |
| .js-accordion-header | Toggle control (<button> or <a>), sole child of the heading |
| aria-controls | Points to the panel region id |
| .js-accordion-inner | Measured content used for height animation |
| role="region" + aria-labelledby | Accessible name for the panel landmark |
Options
Constructor
const accordion = new Accordion(element, {
multiselectable: false, // allow several panels open at once
});| Option | Type | Default | Description |
| --- | --- | --- | --- |
| multiselectable | boolean | false | When false, opening a panel closes the others |
You can also set data-accordion-multiselectable="true" on the root in demos; pass the parsed value into the constructor as above.
Panel data attributes
Set these on .js-accordion-panel:
<div
class="js-accordion-panel"
data-accordion-open="true"
data-accordion-deselect="true"
>
…
</div>| Attribute | Description |
| --- | --- |
| data-accordion-open | Initial open state ("true" / "false") |
| data-accordion-deselect | When "true", an open panel can be collapsed again by its header |
Events
import Accordion from '@19h47/accordion';
const accordion = new Accordion(document.querySelector('.js-accordion'));
accordion.init();
for (const panel of accordion.panels) {
panel.el.addEventListener('Panel.open', ({ detail }) => {
console.log('opened', detail.current);
});
panel.el.addEventListener('Panel.close', ({ detail }) => {
console.log('closed', detail.current);
});
}detail.current is the .js-accordion-panel element.
Keyboard support
| Key | Behavior | | --- | --- | | Space / Enter | Expand or collapse the focused header | | Tab / Shift+Tab | Move through focusable elements in page order | | ↓ / → | Next header (wraps) | | ↑ / ← | Previous header (wraps) | | Home | First header | | End | Last header |
Accessibility
| Role / attribute | Element | Notes |
| --- | --- | --- |
| aria-controls | header | IDREF to the panel region; reflected as ariaControlsElements |
| aria-expanded | header | Updated on open / close |
| aria-disabled | header | Set to "true" when the panel is open and data-accordion-deselect="false" |
| role="region" | panel body | Landmark for the expandable content |
| aria-labelledby | panel body | References the header id |
Development
pnpm install
pnpm dev # Vite playground
pnpm build # library build → dist/Demo
Live example: 19h47.github.io/19h47-accordion · source
