Accordion

Give FeedbackWAI-ARIABundle Size

Introduction

Installation

Base UI components are all available as a single package.

npm install @base_ui/react

Once you have the package installed, import the component.

import { Accordion } from '@base_ui/react/Accordion';

Anatomy

Accordions are implemented using a collection of related components:

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger>Toggle one</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel one content</Accordion.Panel>
  </Accordion.Item>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger>Toggle two</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel two content</Accordion.Panel>
  </Accordion.Item>
</Accordion.Root>

Value

Each Accordion.Item is represented by a value, which by default is its zero-based index by DOM position. The first Item has an implicit value of 0, the second one Item a value of 1, and so on.

The open state of the accordion is represented an array holding the values of all open Items.

You can optionally specify a custom value prop on Item:

<Accordion.Root>
  <Accordion.Item value="one">
    <Accordion.Header>
      <Accordion.Trigger>Toggle one</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel one content</Accordion.Panel>
  </Accordion.Item>
  <Accordion.Item value="two">
    <Accordion.Header>
      <Accordion.Trigger>Toggle two</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel two content</Accordion.Panel>
  </Accordion.Item>
</Accordion.Root>

Default value

When uncontrolled, use the defaultValue prop to set the initial state of the accordion:

<Accordion.Root defaultValue={[0]}>
  <Accordion.Item {/* `value={0}` by default */}>
    <Accordion.Header>
      <Accordion.Trigger>Toggle one</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel one content</Accordion.Panel>
  </Accordion.Item>
  <Accordion.Item {/* `value={1}` by default */}>
    <Accordion.Header>
      <Accordion.Trigger>Toggle two</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel two content</Accordion.Panel>
  </Accordion.Item>
</Accordion.Root>;

// with custom `value`s
<Accordion.Root defaultValue={['a']}>
  <Accordion.Item value="a">
    <Accordion.Header>
      <Accordion.Trigger>Toggle one</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel one content</Accordion.Panel>
  </Accordion.Item>
  <Accordion.Item value="b">
    <Accordion.Header>
      <Accordion.Trigger>Toggle two</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Panel two content</Accordion.Panel>
  </Accordion.Item>
</Accordion.Root>;

Controlled

When controlled, pass the value and onValueChange props to Accordion.Root:

const [value, setValue] = React.useState(['a']);

return (
  <Accordion.Root value={value} onValueChange={setValue}>
    <Accordion.Item value="a">
      <Accordion.Header>
        <Accordion.Trigger>Toggle one</Accordion.Trigger>
      </Accordion.Header>
      <Accordion.Panel>Panel one content</Accordion.Panel>
    </Accordion.Item>
    <Accordion.Item value="b">
      <Accordion.Header>
        <Accordion.Trigger>Toggle two</Accordion.Trigger>
      </Accordion.Header>
      <Accordion.Panel>Panel two content</Accordion.Panel>
    </Accordion.Item>
  </Accordion.Root>
);

Customization

Only one Item open at a time

By default, all accordion items can be opened at the same time. Use the openMultiple prop to only allow one open item at a time:

<Accordion.Root openMultiple={false}>{/* subcomponents */}</Accordion.Root>

At least one Item remains open

Use controlled mode to always keep one Item open:

const [value, setValue] = React.useState([0]);

const handleValueChange = (newValue) => {
  if (newValue.length > 0) {
    setValue(newValue);
  }
};

return (
  <Accordion.Root value={value} onValueChange={handleValueChange}>
    {/* subcomponents */}
  </Accordion.Root>
);

Horizontal

Use the orientation prop to configure a horizontal accordion. In a horizontal accordion, focus will move between Accordion.Triggers with the Right Arrow and Left Arrow keys, instead of Down/Up.

<Accordion.Root orientation="horizontal">{/* subcomponents */}</Accordion.Root>

RTL

Use the direction prop to configure a RTL accordion:

<Accordion.Root direction="rtl">{/* subcomponents */}</Accordion.Root>

When a horizontal accordion is set to direction="rtl", keyboard actions are reversed accordingly - Left Arrow moves focus to the next trigger and Right Arrow moves focus to the previous trigger.

Improving searchability of hidden content

This is not yet supported in Safari and Firefox as of August 2024 and will fall back to the default hidden behavior.

Content hidden by Accordion.Panel components can be made accessible only to a browser's find-in-page functionality with the hiddenUntilFound prop to improve searchability:

<Accordion.Root hiddenUntilFound>{/* subcomponents */}</Accordion.Root>

Alternatively hiddenUntilFound can be passed to Accordion.Panels directly to enable this for only one Item instead of the whole accordion.

We recommend using CSS animations for animated accordions that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a CSS transition applied.

This relies on the HTML hidden="until-found" attribute which only has partial browser support as of August 2024, but automatically falls back to the default hidden state in unsupported browsers.

Animations

Accordion uses Collapsible internally, and can be animated in a similar way.

Three states are available as data attributes to animate the Accordion.Panel:

The component can be animate when opening or closing using either:

Styling

The Accordion.Panel element receives the following CSS variables about its dimensions, which can be used to style animations or transitions:

CSS Animations

CSS animations can be used with two declarations:

.AccordionPanel {
  overflow: hidden;
  animation: slideUp 300ms ease-in;
}

.AccordionPanel[data-open] {
  animation: slideDown 300ms ease-out;
}

@keyframes slideDown {
  from {
    height: 0;
  }
  to {
    height: var(--accordion-panel-height);
  }
}

@keyframes slideUp {
  from {
    height: var(--accordion-panel-height);
  }
  to {
    height: 0;
  }
}

CSS Transitions

When using CSS transitions, styles for the Panel must be applied to three states:

.AccordionPanel {
  overflow: hidden;
  /* The final styles once closed/exited  */
  height: 0;
  transition: height 300ms ease-in;
}

/* The final styles once opened/entered */
.AccordionPanel[data-open] {
  height: var(--accordion-panel-height);
  transition: height 300ms ease-out;
}

/* The initial styles when opening/entering */
.AccordionPanel[data-entering] {
  height: 0;
}

JavaScript Animations

When using external libraries for animation, for example framer-motion, be aware that Accordion.Items hides content using the HTML hidden attribute in the closed state, and does not unmount from the DOM.

function App() {
  const [value, setValue] = useState([0]);
  return (
    <Accordion.Root value={value} onValueChange={setValue}>
      <Accordion.Item>
        <Accordion.Header>
          <Accordion.Trigger>Toggle</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel
          render={
            <motion.div
              key="AccordionPanel"
              initial={false}
              animate={open ? 'open' : 'closed'}
              exit={!open ? 'open' : 'closed'}
              variants={{
                open: {
                  height: 'auto',
                  transition: { duration: 0.3, ease: 'ease-out' },
                },
                closed: {
                  height: 0,
                  transition: { duration: 0.3, ease: 'ease-in' },
                  transitionEnd: { display: 'revert-layer' },
                },
              }}
            />
          }
        >
          This is the content
        </Accordion.Panel>
      </Accordion.Item>
      {/* more accordion items */}
    </Accordion.Root>
  );
}

API Reference

AccordionRoot

PropTypeDefaultDescription
animatedbooltrueIf true, the component supports CSS/JS-based animations and transitions.
classNameunionClass names applied to the element or a function that returns them based on the component's state.
defaultValuearrayOf0The default value representing the currently open Accordion.Item This is the uncontrolled counterpart of value.
disabledboolfalseIf true, the component is disabled.
loopbooltrueIf true, focus will loop when moving focus between Triggers using the arrow keys.
onValueChangefuncCallback fired when an Accordion section is opened or closed. The value representing the involved section is provided as an argument.
openMultiplebooltrueWhether multiple Accordion sections can be opened at the same time
renderunionA function to customize rendering of the component.
valuearrayOfThe value of the currently open Accordion.Item This is the controlled counterpart of defaultValue.

AccordionItem

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
disabledboolfalseIf true, the component is disabled.
onOpenChangefuncCallback fired when the Collapsible is opened or closed.
renderunionA function to customize rendering of the component.

AccordionHeader

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

AccordionTrigger

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
renderunionA function to customize rendering of the component.

AccordionPanel

PropTypeDefaultDescription
classNameunionClass names applied to the element or a function that returns them based on the component's state.
hiddenUntilFoundboolfalseIf true, sets hidden="until-found" when closed. If false, sets hidden when closed.
renderunionA function to customize rendering of the component.

Contents