Accordion
A stack of sections that fold. Opening one closes the last.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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 |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| multiple | boolean | false | Whether 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)[]) => void | — | Called when the open set changes |
| dividers | boolean | true | Separates 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 |
| disabled | boolean | false | Unavailable. Every section stops answering |
| hiddenUntilFound | boolean | false | Keeps closed panels in the DOM so the browser's own page search can find and open them. Overrides keepMounted |
| keepMounted | boolean | false | Keeps closed panels in the DOM. For content that is expensive to build, or form state that should survive being folded away |
| children | ReactNode | — | The AccordionItems |
AccordionItem
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | number | — | Identifies the section to value / defaultValue. Base UI generates one when it is left out |
| title | ReactNode | — | The heading on the fold |
| subtitle | ReactNode | — | A second line under the title |
| startIcon | ReactNode | — | Content before the title |
| action | ReactNode | — | A control pinned to the end of the header, outside the folding button — a button inside a button is markup the browser rewrites |
| disabled | boolean | false | This section stops folding; the rest keep working |
| children | ReactNode | — | The 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.