Accordion
Build vertically collapsing accordions using the native HTML details and summary elements.
Examples
Click the accordions below to expand/collapse the accordion content. Items sharing the same name form a group where only one item can be open at a time — set name on Accordion to apply it to the whole group.
Accordion Item #1
.accordion-body.Accordion Item #2
open prop.Accordion Item #3
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Accordion name="basic-example">
<AccordionItem open>
<AccordionHeader>Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong> It is hidden by default. Just
about any HTML can go within the <code>.accordion-body</code>.
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong> It is shown by default via the{' '}
<code>open</code> prop.
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #3</AccordionHeader>
<AccordionBody>
<strong>This is the third item's accordion body.</strong> It is hidden by default.
</AccordionBody>
</AccordionItem>
</Accordion>
)
}Flush
Add flush to render accordions edge-to-edge with their parent container.
Accordion Item #1
Accordion Item #2
Accordion Item #3
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Accordion flush>
<AccordionItem>
<AccordionHeader>Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #3</AccordionHeader>
<AccordionBody>
<strong>This is the third item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
)
}Always open
Add alwaysOpen to let multiple items stay open at the same time.
Accordion Item #1
Accordion Item #2
Accordion Item #3
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Accordion alwaysOpen>
<AccordionItem open>
<AccordionHeader>Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem open>
<AccordionHeader>Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #3</AccordionHeader>
<AccordionBody>
<strong>This is the third item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
)
}Sizes
Add size="lg" or size="sm" for additional sizes.
Large Accordion Item #1
Large Accordion Item #2
Small Accordion Item #1
Small Accordion Item #2
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<>
<Accordion size="lg" name="size-example-lg" className="mb-md">
<AccordionItem open>
<AccordionHeader>Large Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Large Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
<Accordion size="sm" name="size-example-sm">
<AccordionItem open>
<AccordionHeader>Small Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Small Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
</>
)
}Caret position
Add caretEnd to move the caret icon to the end of the header.
Accordion Item #1
Accordion Item #2
Accordion Item #3
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Accordion caretEnd name="caret-end-example">
<AccordionItem open>
<AccordionHeader>Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This is the first item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This is the second item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHeader>Accordion Item #3</AccordionHeader>
<AccordionBody>
<strong>This is the third item's accordion body.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
)
}Per-item overrides
name and alwaysOpen can be set on Accordion as shared defaults, and overridden on individual AccordionItems — items without their own value fall back to the accordion's setting.
Accordion Item #1
alwaysOpen, so it stays open independently of the group below.Accordion Item #2
name to join a separate group with Accordion Item #3, so opening either one closes the other without affecting Item #1.Accordion Item #3
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from '@chassis-ui/react'
export const Example = () => {
return (
<Accordion name="mixed-groups-example">
<AccordionItem open alwaysOpen>
<AccordionHeader>Accordion Item #1</AccordionHeader>
<AccordionBody>
<strong>This item sets its own</strong> <code>alwaysOpen</code>, so it stays open
independently of the group below.
</AccordionBody>
</AccordionItem>
<AccordionItem name="mixed-groups-example-b" open>
<AccordionHeader>Accordion Item #2</AccordionHeader>
<AccordionBody>
<strong>This item overrides</strong> <code>name</code> to join a separate group with
Accordion Item #3, so opening either one closes the other without affecting Item #1.
</AccordionBody>
</AccordionItem>
<AccordionItem name="mixed-groups-example-b">
<AccordionHeader>Accordion Item #3</AccordionHeader>
<AccordionBody>
<strong>This item shares Item #2's group name.</strong>
</AccordionBody>
</AccordionItem>
</Accordion>
)
}JSON content
Pass an items array to render an accordion from data instead of composed children. Each item accepts header, body, and optional open, name, and alwaysOpen overrides.
Accordion Item #1
Accordion Item #2
Accordion Item #3
import { Accordion } from '@chassis-ui/react'
const items = [
{
id: 'item-1',
header: 'Accordion Item #1',
body: (
<>
<strong>This is the first item's accordion body.</strong> Rendered from a plain data array
instead of composed children.
</>
),
open: true
},
{
id: 'item-2',
header: 'Accordion Item #2',
body: <strong>This is the second item's accordion body.</strong>
},
{
id: 'item-3',
header: 'Accordion Item #3',
body: <strong>This is the third item's accordion body.</strong>,
alwaysOpen: true
}
]
export const Example = () => {
return <Accordion name="json-content-example" items={items} />
}Controlled
A composed AccordionItem already supports the native controlled <details> pattern directly —
pass open and read the new state back from onToggle:
<AccordionItem open={isOpen} onToggle={(e) => setIsOpen(e.currentTarget.open)}>For the items data-driven API, Accordion offers the same thing at the group level:
expandedKeys/defaultExpandedKeys/onExpandedChange track which item keys are open, reflecting
the native <details> elements' actual state (including any the browser closes itself as part of
a shared name group).
Accordion Item #1
Accordion Item #2
Accordion Item #3
import { useState } from 'react'
import { Accordion, Button, ButtonGroup } from '@chassis-ui/react'
const items = [
{
id: 'item-1',
header: 'Accordion Item #1',
body: <strong>This is the first item's accordion body.</strong>
},
{
id: 'item-2',
header: 'Accordion Item #2',
body: <strong>This is the second item's accordion body.</strong>
},
{
id: 'item-3',
header: 'Accordion Item #3',
body: <strong>This is the third item's accordion body.</strong>
}
]
export const Example = () => {
const [expandedKeys, setExpandedKeys] = useState<Array<number | string>>(['item-1'])
return (
<>
<ButtonGroup className="mb-md">
<Button size="sm" onClick={() => setExpandedKeys(items.map((item) => item.id))}>
Expand all
</Button>
<Button size="sm" onClick={() => setExpandedKeys([])}>
Collapse all
</Button>
</ButtonGroup>
<Accordion
alwaysOpen
items={items}
expandedKeys={expandedKeys}
onExpandedChange={setExpandedKeys}
/>
</>
)
}API
Accordion
| Prop | Type | Default |
|---|---|---|
alwaysOpen? | boolean | false |
| Make accordion items stay open when another item is opened. | ||
caretEnd? | boolean | — |
| Move the caret icon to the end of the header. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
defaultExpandedKeys? | (string | number)[] | — |
The keys of the initially expanded items (uncontrolled). Only applies to items rendered via
the items prop — a composed AccordionItem already supports native controlled open/
onToggle directly. | ||
expandedKeys? | (string | number)[] | — |
The keys of the currently expanded items (controlled). Only applies to items rendered via the
items prop — a composed AccordionItem already supports native controlled open/onToggle
directly. | ||
flush? | boolean | — |
| Removes the default background-context, some borders, and some rounded corners to render accordions edge-to-edge with their parent container. | ||
items? | AccordionItemDef[] | — |
| Array of item definitions for data-driven rendering. When provided, children are ignored. | ||
name? | string | — |
The shared group name used by items that don't set their own name. Defaults to an auto-generated id. | ||
onExpandedChange? | ((keys: (string | number)[]) => void) | — |
Callback fired with the updated set of expanded item keys, reflecting the native <details>
elements' actual open state (including items the browser closed itself via a shared name
group). Only fires for items rendered via the items prop. | ||
size? | "sm" | "lg" | — |
| Size the component sm or lg. | ||
AccordionItemDef
Shape of each entry in Accordion's items array (data-driven rendering).
| Prop | Type | Default |
|---|---|---|
alwaysOpen? | boolean | — |
Let this item stay open when another item opens, overriding the accordion's alwaysOpen setting. | ||
body | ReactNode | — |
Body content, rendered inside a AccordionBody. | ||
className? | string | — |
| A string of all className you want applied to this item. | ||
header | ReactNode | — |
Header content, rendered inside a AccordionHeader. | ||
id? | string | number | — |
| React key for this item. Falls back to its index when omitted. | ||
name? | string | — |
The group this item shares with other items so only one can be open at a time, overriding the
accordion's shared name. | ||
onToggle? | ((event: SyntheticEvent<HTMLDetailsElement, Event>) => void) | — |
Callback fired when this item's open state changes. Chained after Accordion's own handling
of expandedKeys/onExpandedChange, so it fires regardless of whether those are used. | ||
open? | boolean | — |
Start the item in the open state (uncontrolled). Ignored for items whose key is included in
expandedKeys/defaultExpandedKeys on the parent Accordion. | ||
AccordionItem
| Prop | Type | Default |
|---|---|---|
alwaysOpen? | boolean | — |
Let this item stay open when another item opens, overriding the accordion's alwaysOpen setting. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
itemKey? | string | number | — |
| @deprecated No longer used. The native details element manages its own state. | ||
name? | string | — |
The group this item shares with other items so only one can be open at a time, overriding the
accordion's shared name. | ||
open? | boolean | — |
| Start the item in the open state. | ||
AccordionHeader
| Prop | Type | Default |
|---|---|---|
className? | string | — |
| A string of all className you want applied to the base component. | ||
AccordionBody
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | — |
Render the single child element in place of this component's own element, with this
component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log
in</Link></Button>. The alternative to component for React Server Components — an element
can be passed from the server to this package's client components, a component reference
(component={Link}) can't. | ||
className? | string | — |
| A string of all className you want applied to the base component. | ||
component? | ElementType | — |
| Component used for the root node. Either a string to use a HTML element or a component. | ||
ref? | any | — |