Components
Accordion
A vertically stacked list of headings that expand and collapse to reveal related content.
About
Accordion presents multiple sections of related content that can be individually expanded and collapsed, letting users scan section headings and reveal only the detail they care about — a streamlined way to manage large amounts of information without overwhelming the page. It is built on React Aria and styled with Thumbprint v2 semantic tokens, so it automatically supports light and dark mode.
Compose an Accordion from AccordionItem children. Each item takes its header text via the heading prop and its panel content as children, and is identified by an id used with expandedKeys and defaultExpandedKeys.
The component implements the WAI-ARIA accordion pattern:
- Each header is a focusable
<button>wrapped in a heading element, witharia-expandedreflecting the open state. - The button and its panel are associated with
aria-controlsandaria-labelledby. EnterorSpacetoggles the focused header, andTabmoves between headers.- Collapsed panels are hidden from the accessibility tree, so screen readers never announce hidden content.
When to use
- FAQ pages, settings panels, or content-heavy pages where showing everything at once would be overwhelming.
- When users need to scan section headings to find relevant information before reading detail.
- For a single expandable section, or independent views to navigate between, prefer a disclosure or tabs instead.
Basic accordion
When expandedKeys is omitted, the component is uncontrolled: React Aria manages the expansion state internally — no state management is required. By default only one item can be open at a time; expanding an item collapses the previously expanded one.
<Accordion> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?"> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> <AccordionItem id="themed" heading="Does it support dark mode?"> Yes. Every color is a v2 semantic token, so dark mode works automatically. </AccordionItem> </Accordion>
Items from an array
As an alternative to composing AccordionItem children, pass Accordion an array of item props via the items prop — convenient for data-driven lists like FAQs. Panel content is provided by each item’s children field, and per-item props like isDisabled flow through. Provide either children or items, not both.
<Accordion items={[ { id: 'accessible', heading: 'Is it accessible?', children: 'Yes. It adheres to the WAI-ARIA design pattern.', }, { id: 'animated', heading: 'Is it animated?', children: "Yes. It's animated by default, but you can disable it if you prefer.", }, ]} />
Expanded by default
Pass defaultExpandedKeys with the ids of the items that should start expanded. Default to the closed state for all items unless one section is clearly most important to show first.
<Accordion defaultExpandedKeys={['animated']}> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?"> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> </Accordion>
Multiple expanded items
Set allowsMultipleExpanded to let items expand and collapse independently, so several sections can be open at the same time — useful when users compare answers. Decide one behavior per page and apply it consistently.
<Accordion allowsMultipleExpanded defaultExpandedKeys={['accessible', 'animated']}> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?"> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> <AccordionItem id="themed" heading="Does it support dark mode?"> Yes. Every color is a v2 semantic token, so dark mode works automatically. </AccordionItem> </Accordion>
Controlled expansion
Provide expandedKeys and onExpandedChange to control which items are expanded. The onExpandedChange callback receives the new Set of expanded item ids.
function ControlledAccordion() { const [expandedKeys, setExpandedKeys] = React.useState(new Set(['accessible'])); return ( <Accordion expandedKeys={expandedKeys} onExpandedChange={setExpandedKeys}> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?"> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> </Accordion> ); }
Disabled items
Set isDisabled on an AccordionItem to prevent it from being toggled, or on the Accordion to disable every item.
<Accordion> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="disabled" heading="Is this item disabled?" isDisabled> This content cannot be revealed. </AccordionItem> </Accordion>
Disabling the animation
Expanding and collapsing animates the panel height and caret rotation by default. Set isAnimated={false} on an item to make it snap open and closed instead. Users with a reduced-motion preference (prefers-reduced-motion) never see the animation regardless.
<Accordion> <AccordionItem id="animated" heading="Is it animated?" isAnimated={false}> Not this one. Expanding and collapsing snap instantly. </AccordionItem> </Accordion>
Heading levels
Each header button is wrapped in an <h3> by default. Use headingLevel to pick the level (2–6) that fits the page’s heading outline — for example 2 when accordion sections sit directly under the page title. The visual style is unaffected.
<Accordion> <AccordionItem id="accessible" heading="Is it accessible?" headingLevel={2}> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?" headingLevel={2}> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> </Accordion>
Dark mode
Every color in the component is a v2 semantic token, so the accordion renders correctly wherever the v2 dark theme is active (data-theme="dark" on an ancestor) with no extra props.
<div data-theme="dark"> <Accordion defaultExpandedKeys={['accessible']}> <AccordionItem id="accessible" heading="Is it accessible?"> Yes. It adheres to the WAI-ARIA design pattern. </AccordionItem> <AccordionItem id="animated" heading="Is it animated?"> Yes. It's animated by default, but you can disable it if you prefer. </AccordionItem> </Accordion> </div>
Responsive behavior
The accordion is a full-width component: rows stretch to fill their container at every viewport size, with the caret pinned to the end of the header row. Long headings wrap onto multiple lines while the caret stays vertically centered. The 16px header padding keeps each row comfortably above the 44px minimum touch-target size on mobile. The example below constrains the accordion to a phone-sized 375px container to show the wrapping behavior; resize the browser to watch the rows adapt.
<div style={{ maxWidth: '375px' }}> <Accordion> <AccordionItem id="cost" heading="How much does it cost to remodel a bathroom, and what factors affect the price?" > The average bathroom remodel costs between $6,000 and $16,000, depending on the size of the bathroom, the quality of the fixtures and finishes, and how much of the existing plumbing and electrical work needs to change. </AccordionItem> <AccordionItem id="timeline" heading="How long does a typical project take?"> Most bathroom remodels take two to three weeks of active work, plus time up front for design, permitting, and ordering materials. </AccordionItem> </Accordion> </div>
Props
AccordionItem
idrequiredUniquely identifies the item within the accordion, matching the ids used in
expandedKeysanddefaultExpandedKeys. Maps to React Aria'sid.Typestring | numberheadingrequiredThe heading shown in the always-visible header row. Rendered inside the toggle button per the WAI-ARIA accordion pattern.
TypeReact.ReactNodechildrenThe panel content revealed when the item is expanded.
TypeReact.ReactNodeheadingLevelThe semantic heading level (
<h2>–<h6>) wrapping the toggle button. Pick the level that fits the page's heading outline; the visual style is unaffected.Type2 | 3 | 4 | 5 | 6Default3isDisabledDisables the item: the header does not accept interaction and renders in the disabled treatment.
TypebooleanisAnimatedWhether expanding and collapsing animates the panel height and caret rotation. Users with a reduced-motion preference never see the animation regardless. Maps to the
[data-animated]attribute.TypebooleanDefaulttrueonExpandedChangeFunction that runs when this item expands or collapses. It receives the new expanded state. Maps to React Aria's
onExpandedChange.Type(isExpanded: boolean) => voiddataTestIdA selector hook into the React component for use in automated testing environments.
Typestring
Accordion
childrenThe
AccordionItems to render, stacked vertically with a divider between items. Provide eitherchildrenoritems, not both.TypeReact.ReactNodeitemsRenders the accordion from an array of
AccordionItemPropsas an alternative to composingAccordionItemchildren. Panel content is provided by each item'schildrenfield. Provide eitherchildrenoritems, not both.TypeAccordionItemProps[]allowsMultipleExpandedWhether multiple items can be expanded at the same time. When
false(the default), expanding an item collapses the previously expanded one. Maps to React Aria'sallowsMultipleExpanded.TypebooleanDefaultfalseexpandedKeysThe currently expanded items, matching the
ids ofAccordionItems. Use together withonExpandedChangefor a controlled component. When omitted, the component is uncontrolled: React Aria manages the expansion state internally, starting atdefaultExpandedKeysand updating it as the user toggles items — noonExpandedChangehandler or state management is required. Maps to React Aria'sexpandedKeys.TypeIterable<string | number>defaultExpandedKeysThe items expanded initially when the component is uncontrolled (no
expandedKeys), matching theids ofAccordionItems. Has no effect whenexpandedKeysis provided. Maps to React Aria'sdefaultExpandedKeys.TypeIterable<string | number>onExpandedChangeFunction that runs when items expand or collapse. It receives the new set of expanded item
ids. Maps to React Aria'sonExpandedChange.Type(keys: Set<string | number>) => voidisDisabledDisables every item in the accordion.
TypebooleanidThe
idis added to the underlying element as an HTML attribute.TypestringdataTestIdA selector hook into the React component for use in automated testing environments.
Typestring