Thumbprint logo

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, with aria-expanded reflecting the open state.
  • The button and its panel are associated with aria-controls and aria-labelledby.
  • Enter or Space toggles the focused header, and Tab moves 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 (26) 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

  • id
    required

    Uniquely identifies the item within the accordion, matching the ids used in expandedKeys and defaultExpandedKeys. Maps to React Aria's id.

    Type
    string | number
  • heading
    required

    The heading shown in the always-visible header row. Rendered inside the toggle button per the WAI-ARIA accordion pattern.

    Type
    React.ReactNode
  • children

    The panel content revealed when the item is expanded.

    Type
    React.ReactNode
  • headingLevel

    The semantic heading level (<h2><h6>) wrapping the toggle button. Pick the level that fits the page's heading outline; the visual style is unaffected.

    Type
    2 | 3 | 4 | 5 | 6
    Default
    3
  • isDisabled

    Disables the item: the header does not accept interaction and renders in the disabled treatment.

    Type
    boolean
  • isAnimated

    Whether 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.

    Type
    boolean
    Default
    true
  • onExpandedChange

    Function that runs when this item expands or collapses. It receives the new expanded state. Maps to React Aria's onExpandedChange.

    Type
    (isExpanded: boolean) => void
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

Accordion

  • children

    The AccordionItems to render, stacked vertically with a divider between items. Provide either children or items, not both.

    Type
    React.ReactNode
  • items

    Renders the accordion from an array of AccordionItemProps as an alternative to composing AccordionItem children. Panel content is provided by each item's children field. Provide either children or items, not both.

    Type
    AccordionItemProps[]
  • allowsMultipleExpanded

    Whether 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's allowsMultipleExpanded.

    Type
    boolean
    Default
    false
  • expandedKeys

    The currently expanded items, matching the ids of AccordionItems. Use together with onExpandedChange for a controlled component. When omitted, the component is uncontrolled: React Aria manages the expansion state internally, starting at defaultExpandedKeys and updating it as the user toggles items — no onExpandedChange handler or state management is required. Maps to React Aria's expandedKeys.

    Type
    Iterable<string | number>
  • defaultExpandedKeys

    The items expanded initially when the component is uncontrolled (no expandedKeys), matching the ids of AccordionItems. Has no effect when expandedKeys is provided. Maps to React Aria's defaultExpandedKeys.

    Type
    Iterable<string | number>
  • onExpandedChange

    Function that runs when items expand or collapse. It receives the new set of expanded item ids. Maps to React Aria's onExpandedChange.

    Type
    (keys: Set<string | number>) => void
  • isDisabled

    Disables every item in the accordion.

    Type
    boolean
  • id

    The id is added to the underlying element as an HTML attribute.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string