Skip to content

Accordion

A stack of sections that fold. Opening one closes the last.

tsx
import { Accordion, AccordionItem } from 'neba';

<Accordion defaultValue={['billing']}>
  <AccordionItem value="billing" title="How does billing work?" subtitle="Plans and invoices">
    You are charged on the first of each month.
  </AccordionItem>
  <AccordionItem value="regions" title="Where do builds run?">
    In the region closest to the default branch.
  </AccordionItem>
</Accordion>;

Props

Accordion

PropTypeDefaultDescription
variantshared'solid' | 'outline' | 'text''outline'Weight of the surface: filled, hairline, or none
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The sheet's radius and each section's padding — the same thing size means on Box
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
densityshared'default' | 'compact''default'Padding only — never the height, never the type scale
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 means no shadow at all
multiplebooleanfalseWhether more than one section may be open. Off by default: closing the last as you open the next is the whole difference between an accordion and a stack of collapsibles
value(string | number)[]Which sections are open, for a controlled accordion
defaultValue(string | number)[]Which start open
onValueChange(value: (string | number)[]) => voidCalled when the open set changes
dividersbooleantrueSeparates the sections with a hairline rather than space. The opposite default from List: an accordion of tiles is a stack of cards, not one thing
disabledbooleanfalseUnavailable. Every section stops answering
hiddenUntilFoundbooleanfalseKeeps closed panels in the DOM so the browser's own page search can find and open them. Overrides keepMounted
keepMountedbooleanfalseKeeps closed panels in the DOM. For content that is expensive to build, or form state that should survive being folded away
childrenReactNodeThe AccordionItems

AccordionItem

PropTypeDefaultDescription
valuestring | numberIdentifies the section to value / defaultValue. Base UI generates one when it is left out
titleReactNodeThe heading on the fold
subtitleReactNodeA second line under the title
startIconReactNodeContent before the title
actionReactNodeA control pinned to the end of the header, outside the folding button — a button inside a button is markup the browser rewrites
disabledbooleanfalseThis section stops folding; the rest keep working
childrenReactNodeThe body

Examples

Variants

The sheet is never dyed — the same as Box and List. A container holds other people's content, and that content arrives with its own colours. text is the one to reach for inside a card: the card is already a sheet, and a second bordered rectangle inside it is just a second rectangle.

multiple, dividers, and the control beside the header

Sizes

Why multiple is off by default

It is the whole difference between an accordion and a stack of collapsibles. Closing the last section as you open the next is what keeps the page from growing under the reader. Turn multiple on when the sections are a checklist rather than a set of mutually exclusive answers.

dividers takes the opposite default from List. A list of tiles is a list; an accordion of tiles is a stack of cards that happen to fold. The rule is what says the sections are parts of one thing.

The height moves. Nothing is transformed

The panel's height is animated, which looks like an exception to the rule against moving things and is not.

Nothing is transformed, no text is resampled, and the content does not shift relative to the panel it is in — the panel is a window opening onto it. An accordion whose sections appear instantly is a page that jumps, which is the failure the rule exists to prevent in the first place.

A header is two things, not one

action sits outside the folding button. A header that both folds and holds a switch has two things to press, and one of them cannot be nested inside the other — a <button> inside a <button> is markup the browser rewrites on parse. It is the same shape Chip and ListItem use.

Accessibility

Base UI owns the button / region pairing and the aria-controls / aria-expanded wiring between them. What is left to decide is whether title should be a real heading — for a section that belongs in the document outline, pass title={<h3>Billing</h3>}. .neba-title strips the browser's own size and margins, so the type scale holds.

hiddenUntilFound keeps closed panels in the DOM so the browser's own find-on-page can locate and open them. Worth turning on for an FAQ.

Released under the MIT License