Skip to content

Accordion

Collapsible content sections.

Built with TypeScript, accessible by default, and fully composable.
<Card padding={4}>
<Accordion aria-label="FAQ" defaultValue="features">
<AccordionItem trigger="Features" value="features">
<Text>
Built with TypeScript, accessible by default, and fully composable.
</Text>
</AccordionItem>
<AccordionItem trigger="Installation" value="installation">
<Text>
Install via npm, pnpm, or yarn. No additional peer dependencies.
</Text>
</AccordionItem>
<AccordionItem trigger="Usage" value="usage">
<Text>
Import components directly. Tree-shaking is supported out of the
box.
</Text>
</AccordionItem>
</Accordion>
</Card>
Built with TypeScript, accessible by default, and fully composable.
Import components directly. Tree-shaking is supported out of the box.
<Card padding={4}>
<Accordion
aria-label="Documentation"
defaultValue={['features', 'usage']}
type="multiple">
<AccordionItem trigger="Features" value="features">
<Text>
Built with TypeScript, accessible by default, and fully composable.
</Text>
</AccordionItem>
<AccordionItem trigger="Installation" value="installation">
<Text>
Install via npm, pnpm, or yarn. No additional peer dependencies.
</Text>
</AccordionItem>
<AccordionItem trigger="Usage" value="usage">
<Text>
Import components directly. Tree-shaking is supported out of the
box.
</Text>
</AccordionItem>
</Accordion>
</Card>
<Card padding={4}>
<Accordion aria-label="Sections" type="single">
<AccordionItem trigger="Section A" value="a">
<Text>Content for section A.</Text>
</AccordionItem>
<AccordionItem trigger="Section B" value="b">
<Text>Content for section B.</Text>
</AccordionItem>
<AccordionItem trigger="Section C" value="c">
<Text>Content for section C.</Text>
</AccordionItem>
</Accordion>
</Card>
General settings content.
<Card padding={4}>
<Accordion aria-label="Settings" defaultValue="general">
<AccordionItem trigger="General" value="general">
<Text>General settings content.</Text>
</AccordionItem>
<AccordionItem isDisabled trigger="Advanced (locked)" value="advanced">
<Text>This section is disabled.</Text>
</AccordionItem>
<AccordionItem trigger="About" value="about">
<Text>About this application.</Text>
</AccordionItem>
</Accordion>
</Card>
Built with TypeScript, accessible by default, and fully composable.
function Controlled() {
const [value, setValue] = useState<string | null>('features');
return (
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
<div style={{display: 'flex', gap: '0.5rem'}}>
<Button
label="Open Features"
onClick={() => {
setValue('features');
}}
variant="secondary"
/>
<Button
label="Open Usage"
onClick={() => {
setValue('usage');
}}
variant="secondary"
/>
<Button
label="Close all"
onClick={() => {
setValue(null);
}}
variant="ghost"
/>
</div>
<Card padding={4}>
<Accordion
aria-label="FAQ"
onChange={setValue}
type="single"
value={value}>
<AccordionItem trigger="Features" value="features">
<Text>
Built with TypeScript, accessible by default, and fully
composable.
</Text>
</AccordionItem>
<AccordionItem trigger="Installation" value="installation">
<Text>
Install via npm, pnpm, or yarn. No additional peer dependencies.
</Text>
</AccordionItem>
<AccordionItem trigger="Usage" value="usage">
<Text>
Import components directly. Tree-shaking is supported out of the
box.
</Text>
</AccordionItem>
</Accordion>
</Card>
</div>
);
}
Built with TypeScript, accessible by default, and fully composable.
function ControlledMultiple() {
const [value, setValue] = useState<string[]>(['features']);
return (
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
<div style={{display: 'flex', gap: '0.5rem'}}>
<Button
label="Open all"
onClick={() => {
setValue(['features', 'installation', 'usage']);
}}
variant="secondary"
/>
<Button
label="Close all"
onClick={() => {
setValue([]);
}}
variant="ghost"
/>
</div>
<Card padding={4}>
<Accordion
aria-label="FAQ"
onChange={setValue}
type="multiple"
value={value}>
<AccordionItem trigger="Features" value="features">
<Text>
Built with TypeScript, accessible by default, and fully
composable.
</Text>
</AccordionItem>
<AccordionItem trigger="Installation" value="installation">
<Text>
Install via npm, pnpm, or yarn. No additional peer dependencies.
</Text>
</AccordionItem>
<AccordionItem trigger="Usage" value="usage">
<Text>
Import components directly. Tree-shaking is supported out of the
box.
</Text>
</AccordionItem>
</Accordion>
</Card>
</div>
);
}
Built with TypeScript, accessible by default, and fully composable.
function ItemsInCards() {
const [value, setValue] = useState<string | null>('features');
return (
<Accordion
aria-label="FAQ"
onChange={setValue}
type="single"
value={value}>
<Card padding={4}>
<AccordionItem trigger="Features" value="features">
<Text>
Built with TypeScript, accessible by default, and fully
composable.
</Text>
</AccordionItem>
</Card>
<Card padding={4}>
<AccordionItem trigger="Installation" value="installation">
<Text>
Install via npm, pnpm, or yarn. No additional peer dependencies.
</Text>
</AccordionItem>
</Card>
<Card padding={4}>
<AccordionItem trigger="Usage" value="usage">
<Text>
Import components directly. Tree-shaking is supported out of the
box.
</Text>
</AccordionItem>
</Card>
</Accordion>
);
}
This is the collapsible content. It can contain any elements including text, images, forms, or other components.
<Card padding={4}>
<Collapsible trigger="Toggle details">
<Text>
This is the collapsible content. It can contain any elements including
text, images, forms, or other components.
</Text>
</Collapsible>
</Card>
<Card padding={4}>
<Collapsible isDefaultOpen={false} trigger="Toggle details">
<Text>This content starts hidden.</Text>
</Collapsible>
</Card>
This content cannot be toggled.
<Card padding={4}>
<Collapsible isDisabled trigger="Disabled section">
<Text>This content cannot be toggled.</Text>
</Collapsible>
</Card>
function Controlled() {
const [isOpen, setIsOpen] = useState(false);
return (
<div style={{display: 'flex', flexDirection: 'column', gap: '1rem'}}>
<Button
label={isOpen ? 'Close externally' : 'Open externally'}
onClick={() => {
setIsOpen(prev => !prev);
}}
variant="secondary"
/>
<Card padding={4}>
<Collapsible
isOpen={isOpen}
onOpenChange={setIsOpen}
trigger="Controlled section">
<Text>
This section is controlled by external state. Both the trigger and
the button above can toggle it.
</Text>
</Collapsible>
</Card>
</div>
);
}
  • TypeScript support
  • Accessible by default
  • Composable API
<Card padding={4}>
<Collapsible
trigger={
<span style={{display: 'flex', alignItems: 'center', gap: '0.5rem'}}>
<span style={{fontSize: '1.25rem'}}>📋</span>
<span>Project requirements</span>
<span
style={{
fontSize: '0.75rem',
padding: '0.125rem 0.5rem',
borderRadius: '999px',
backgroundColor: 'var(--silver-colors-bg-subtle)',
}}>
3 items
</span>
</span>
}>
<ul style={{margin: 0, paddingLeft: '1.5rem'}}>
<li>TypeScript support</li>
<li>Accessible by default</li>
<li>Composable API</li>
</ul>
</Collapsible>
</Card>
<Card padding={4}>
<Collapsible isDefaultOpen={false} trigger="Terms and conditions">
<div>
{Array.from({length: 8}, (_, i) => (
<Text key={i}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
</Text>
))}
</div>
</Collapsible>
</Card>
Content for section A.
Content for section B.
<Card padding={4}>
<div style={{display: 'flex', flexDirection: 'column', gap: '0.5rem'}}>
<Collapsible trigger="Section A">
<Text>Content for section A.</Text>
</Collapsible>
<Collapsible trigger="Section B">
<Text>Content for section B.</Text>
</Collapsible>
<Collapsible isDefaultOpen={false} trigger="Section C (starts closed)">
<Text>Content for section C.</Text>
</Collapsible>
</div>
</Card>
This section contains a nested collapsible.
<Card padding={4}>
<Collapsible trigger="Outer section">
<Text>This section contains a nested collapsible.</Text>
<Collapsible isDefaultOpen={false} trigger="Inner section">
<Text>This is the nested content.</Text>
</Collapsible>
</Collapsible>
</Card>
This collapsible has no Card wrapper.
<Collapsible trigger="Standalone (no Card wrapper)">
<Text>This collapsible has no Card wrapper.</Text>
</Collapsible>

When type: "single"

PropTypeDefaultDescription
aria-labelstringAccessible label for the accordion group. Provide either aria-label or aria-labelledby, not both.
aria-labelledbystringID of an element that labels the accordion group. Provide either aria-label or aria-labelledby, not both.
children*ReactNodeOne or more AccordionItem elements.
classNamestringAdditional CSS class names applied to the root element.
data-testidstringTest ID applied to the root element.
refRef<HTMLDivElement>Ref forwarded to the root element.
styleCSSPropertiesInline styles applied to the root element.
defaultValuestring | nullItem value that is open on initial render, or null for all closed. Ignored when value is provided.
onChange(value: string | null) => voidCalled when the open item changes. Receives the item value string, or null when all items are closed.
type'single'Only one item can be open at a time. This is the default.
valuestring | nullControls which item is open externally. Pass null to close all items. When set, the component becomes controlled and onChange should be provided.

When type: "multiple"

PropTypeDefaultDescription
aria-labelstringAccessible label for the accordion group. Provide either aria-label or aria-labelledby, not both.
aria-labelledbystringID of an element that labels the accordion group. Provide either aria-label or aria-labelledby, not both.
children*ReactNodeOne or more AccordionItem elements.
classNamestringAdditional CSS class names applied to the root element.
data-testidstringTest ID applied to the root element.
refRef<HTMLDivElement>Ref forwarded to the root element.
styleCSSPropertiesInline styles applied to the root element.
defaultValuestring[]Item values that are open on initial render. Ignored when value is provided.
onChange(value: string[]) => voidCalled when the set of open items changes. Receives an array of open item values.
type*'multiple'Multiple items can be open simultaneously.
valuestring[]Controls which items are open externally. When set, the component becomes controlled and onChange should be provided. Memoize array values to avoid unnecessary re-renders.

A single expandable section within an `Accordion`, or a standalone collapsible panel when used outside of an `Accordion` context. Prefer the `Collapsible` wrapper for standalone usage.

PropTypeDefaultDescription
childrenReactNodeContent revealed when the item is open.
classNamestringAdditional CSS class names applied to the root element.
data-testidstringTest ID applied to the root element.
isDefaultOpenbooleanWhether the item is open on initial render. Ignored when isOpen is provided or the item is inside an Accordion. Defaults to true.
isDisabledbooleanWhether the trigger is disabled. Prevents toggling and applies disabled styling.
isOpenbooleanControls the open state externally. When set, the component becomes controlled and onOpenChange should be provided. Ignored when the item is inside an Accordion.
onOpenChange(isOpen: boolean) => voidCalled when the open state changes from user interaction.
refRef<HTMLDivElement>Ref forwarded to the root element.
styleCSSPropertiesInline styles applied to the root element.
trigger*ReactNodeContent rendered as the clickable trigger. Must be phrasing content (text, <span>, <strong>, icons, etc.) since it is placed inside a <button> element.
valuestringUnique identifier used by a parent Accordion to track which items are open. Required when used inside an Accordion, ignored otherwise.

A standalone disclosure widget that toggles the visibility of a content panel. Use `Collapsible` for individual expand/collapse sections. For coordinated groups where only one (or a subset) can be open at a time, use `Accordion` with `AccordionItem` instead.

PropTypeDefaultDescription
childrenReactNodeContent revealed when the collapsible is open.
classNamestringAdditional CSS class names applied to the root element.
data-testidstringTest ID applied to the root element.
isDefaultOpenbooleanWhether the collapsible is open on initial render. Ignored when isOpen is provided. Defaults to true.
isDisabledbooleanWhether the trigger is disabled. Prevents toggling and applies disabled styling.
isOpenbooleanControls the open state externally. When set, the component becomes controlled and onOpenChange should be provided to handle toggling.
onOpenChange(isOpen: boolean) => voidCalled when the open state changes, either from a user click on the trigger or from a keyboard interaction.
refRef<HTMLDivElement>Ref forwarded to the root element.
styleCSSPropertiesInline styles applied to the root element.
trigger*ReactNodeContent rendered as the clickable trigger that toggles the panel. Must be phrasing content (text, <span>, <strong>, icons, etc.) since it is placed inside a <button> element.