Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Accordion

Stacked headings that each reveal a section of content — a heading, a button, and a labelled region, exactly as the WAI-ARIA Accordion pattern specifies.

Yes. It follows the WAI-ARIA Accordion pattern: a heading wrapping a button, and a labelled region for the panel.

Yes, via a grid-template-rows transition — and not at all under prefers-reduced-motion.

Pass type="multiple".

Usage#

import {
  Accordion,
  AccordionContent,
  AccordionHeader,
  AccordionItem,
  AccordionTrigger,
} from "neelam-ui";
 
<Accordion defaultValue="item-1">
  <AccordionItem value="item-1">
    <AccordionHeader>
      <AccordionTrigger>Is it accessible?</AccordionTrigger>
    </AccordionHeader>
    <AccordionContent>Yes.</AccordionContent>
  </AccordionItem>
</Accordion>

AccordionHeader wrapping AccordionTrigger is not optional decoration — the pattern is a heading containing a button, which is what puts each section in the document outline.

Single or multiple#

type="single" (the default) keeps at most one item open, and value is a string. collapsible lets you close the open one by activating it again; without it, one item is always open.

type="multiple" allows any number open at once, and value becomes an array:

Ships within two business days.

Free returns within 30 days.

Two years, parts and labour.

Heading levels#

AccordionHeader renders an <h3>. Nest the accordion under an appropriate heading in your own outline so the levels descend without skipping — the same consideration Card's title has.

Closed content stays mounted#

AccordionContent animates with a grid-template-rows transition from 0fr to 1fr, because height: auto is not a transitionable value. That means closed content remains in the DOM.

Closed panels are inert, not just clipped

Content hidden by the collapsed row would otherwise still be tabbable and still exposed to assistive tech. AccordionContent marks itself inert while closed, so it is genuinely unreachable — the same problem Sidebar solves when it collapses.

This differs from Tabs, which unmounts inactive panels outright: tabs are not animated, so there is nothing to keep them around for. Here, a panel's internal state does survive being closed.

Controlled#

const [open, setOpen] = useState("item-1");
 
<Accordion value={open} onValueChange={setOpen}>…</Accordion>

For type="multiple", value and onValueChange work with string[].

Keyboard#

Keyboard shortcuts
KeyBehaviour
TabMoves to the next trigger, then into an open panel's content. Closed panels are inert and skipped.
EnterSpaceToggles the focused section — native button activation.

Arrow keys are not bound here

The APG lists arrow-key movement between headers as optional, and each trigger is an ordinary tab stop instead. That keeps browser find-in-page and normal tabbing behaving predictably across a long page of sections.

Accessibility#

  • The structure is the pattern: <h3><button aria-expanded aria-controls><div role="region" aria-labelledby>. Each panel is named by its own trigger.
  • aria-expanded on the trigger is what announces open or closed; the chevron is aria-hidden decoration.
  • Closed panels are inert, so collapsed content is never a set of invisible tab stops.
  • The expand transition is dropped entirely under prefers-reduced-motion (WCAG 2.3.3) — sections snap open instead.

API reference#

Accordion (single)#

Props for AccordionSingle
PropTypeDefault
collapsible

Allows closing the open item by activating it again. Defaults to `false`.

boolean
defaultValuestring
onValueChange((value: string | undefined) => void)
type

At most one item open at a time. This is the default.

"single"
value

Controls which item is open. Omit to let the accordion manage its own state.

string

Accordion (multiple)#

Props for AccordionMultiple
PropTypeDefault
typerequired

Any number of items can be open at once.

"multiple"
defaultValuestring[]
onValueChange((value: string[]) => void)
valuestring[]

AccordionItem#

Props for AccordionItem
PropTypeDefault
valuerequired

Identifies this item — the value passed around by `Accordion`'s `value`/`onValueChange`.

string