Accordion
A group of native <details>/<summary> disclosures. Every tier, including a11y, is server-renderable: no 'use client' anywhere, no hooks, no JavaScript at all. Open/close, exclusive selection and the expanded/collapsed state announced to assistive tech all come from the browser's native <details> behaviour.
Example (a11y tier)
How long does shipping take?
Standard shipping takes 5-7 business days.
What is your return policy?
Returns are accepted within 30 days of delivery, unused and in original packaging.
How do I contact support?
Email support@example.com, or use the chat widget in the bottom corner.
Import
import { Accordion, Disclosure } from 'abaabil/accordion' // structure onlyimport { Accordion, Disclosure } from 'abaabil/accordion/styled' // + CSSimport { Accordion_a11y, Disclosure } from 'abaabil/accordion/a11y' // + optional group labelEvery tier renders with zero client JavaScript
Unlike every other component on this site, the accordion's a11y tier adds no client-only behaviour at all, because it needs none: <details> already has the correct implicit semantics, and <summary> already has the correct implicit interactive role. Exclusive open, so opening one panel closes its siblings, comes entirely from giving each <details> the same native name attribute; the a11y tier's only addition is an optional label, which applies role="group" and aria-label to the wrapper, and only when a consumer actually supplies one (an unnamed group announces as just "group", which is worse than not grouping at all, so this is opt-in rather than automatic).
The honest limitation: no headingLevel
This component deliberately offers no way to give each panel heading semantics. <summary> has an implicit ARIA role of button, and a heading nested inside a button is not reliably exposed to assistive technology: VoiceOver, for one, does not expose a heading nested inside <summary> as a heading at all. The reverse, wrapping <summary> in a heading element, is not valid either, because <summary> must be the literal first child of <details> for the browser to recognize it as the disclosure trigger.
An API that emitted a heading here would let a consumer believe they had solved heading navigation for screen reader users when they had not, so this component does not offer one. Consumers who need reliable heading navigation across sections need the button-in-heading accordion pattern instead, an explicit heading wrapping a button with aria-expanded and aria-controls, which is a different, ARIA-driven widget. This component is a native disclosure/accordion, not a substitute for that pattern.
Props
| Prop | Type | Default | Tier | Description |
|---|---|---|---|---|
items | Array<{key?, summary, children?}> | — | all | Accordion only. One disclosure per item. |
name | string | 'abaabil-accordion' | all | Accordion only. Shared native group name for exclusive open/close; pass name={undefined} to opt out. |
label | string | — | a11y | Accordion only. Accessible name for the group; applies role="group" and aria-label. Omitted entirely when not given. |
summary | ReactNode | — | all | Disclosure only. Content for the <summary>. |
summaryClassName | string | — | all | Disclosure only. Merged with the summary's base class. |
className | string | — | all | Merged with the base class, on either component. |
| ...props | — | — | all | Everything else spreads onto the <details> (Disclosure) or wrapper <div> (Accordion). |