Accordion
Collapsible content sections.
Examples
Section titled “Examples”Single
Section titled “Single”Built with TypeScript, accessible by default, and fully composable.
Install via npm, pnpm, or yarn. No additional peer dependencies.
Import components directly. Tree-shaking is supported out of the box.
<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>Multiple
Section titled “Multiple”Built with TypeScript, accessible by default, and fully composable.
Install via npm, pnpm, or yarn. No additional peer dependencies.
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>All Collapsed
Section titled “All Collapsed”Content for section A.
Content for section B.
Content for section C.
<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>With Disabled Item
Section titled “With Disabled Item”General settings content.
This section is disabled.
About this application.
<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>Controlled
Section titled “Controlled”Built with TypeScript, accessible by default, and fully composable.
Install via npm, pnpm, or yarn. No additional peer dependencies.
Import components directly. Tree-shaking is supported out of the box.
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> );}Controlled Multiple
Section titled “Controlled Multiple”Built with TypeScript, accessible by default, and fully composable.
Install via npm, pnpm, or yarn. No additional peer dependencies.
Import components directly. Tree-shaking is supported out of the box.
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> );}Items In Cards
Section titled “Items In Cards”Built with TypeScript, accessible by default, and fully composable.
Install via npm, pnpm, or yarn. No additional peer dependencies.
Import components directly. Tree-shaking is supported out of the box.
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> );}Collapsible examples
Section titled “Collapsible examples”Default
Section titled “Default”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>Initially Closed
Section titled “Initially Closed”This content starts hidden.
<Card padding={4}> <Collapsible isDefaultOpen={false} trigger="Toggle details"> <Text>This content starts hidden.</Text> </Collapsible></Card>Disabled
Section titled “Disabled”This content cannot be toggled.
<Card padding={4}> <Collapsible isDisabled trigger="Disabled section"> <Text>This content cannot be toggled.</Text> </Collapsible></Card>Controlled
Section titled “Controlled”This section is controlled by external state. Both the trigger and the button above can toggle it.
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> );}Custom Trigger
Section titled “Custom Trigger”- 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>Long Content
Section titled “Long Content”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.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.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.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.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.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.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.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.
<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>Multiple
Section titled “Multiple”Content for section A.
Content for section B.
Content for section C.
<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>Nested
Section titled “Nested”This section contains a nested collapsible.
This is the nested content.
<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>Standalone
Section titled “Standalone”This collapsible has no Card wrapper.
<Collapsible trigger="Standalone (no Card wrapper)"> <Text>This collapsible has no Card wrapper.</Text></Collapsible>Accordion
Section titled “Accordion”When type: "single"
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible label for the accordion group. Provide either aria-label or aria-labelledby, not both. |
aria-labelledby | string | — | ID of an element that labels the accordion group. Provide either aria-label or aria-labelledby, not both. |
children* | ReactNode | — | One or more AccordionItem elements. |
className | string | — | Additional CSS class names applied to the root element. |
data-testid | string | — | Test ID applied to the root element. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the root element. |
style | CSSProperties | — | Inline styles applied to the root element. |
defaultValue | string | null | — | Item value that is open on initial render, or null for all closed. Ignored when value is provided. |
onChange | (value: string | null) => void | — | Called 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. |
value | string | null | — | Controls 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"
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | — | Accessible label for the accordion group. Provide either aria-label or aria-labelledby, not both. |
aria-labelledby | string | — | ID of an element that labels the accordion group. Provide either aria-label or aria-labelledby, not both. |
children* | ReactNode | — | One or more AccordionItem elements. |
className | string | — | Additional CSS class names applied to the root element. |
data-testid | string | — | Test ID applied to the root element. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the root element. |
style | CSSProperties | — | Inline styles applied to the root element. |
defaultValue | string[] | — | Item values that are open on initial render. Ignored when value is provided. |
onChange | (value: string[]) => void | — | Called when the set of open items changes. Receives an array of open item values. |
type* | 'multiple' | — | Multiple items can be open simultaneously. |
value | string[] | — | 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. |
AccordionItem
Section titled “AccordionItem”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.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content revealed when the item is open. |
className | string | — | Additional CSS class names applied to the root element. |
data-testid | string | — | Test ID applied to the root element. |
isDefaultOpen | boolean | — | Whether the item is open on initial render. Ignored when isOpen is provided or the item is inside an Accordion. Defaults to true. |
isDisabled | boolean | — | Whether the trigger is disabled. Prevents toggling and applies disabled styling. |
isOpen | boolean | — | Controls 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) => void | — | Called when the open state changes from user interaction. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the root element. |
style | CSSProperties | — | Inline styles applied to the root element. |
trigger* | ReactNode | — | Content rendered as the clickable trigger. Must be phrasing content (text, <span>, <strong>, icons, etc.) since it is placed inside a <button> element. |
value | string | — | Unique identifier used by a parent Accordion to track which items are open. Required when used inside an Accordion, ignored otherwise. |
Collapsible
Section titled “Collapsible”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.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content revealed when the collapsible is open. |
className | string | — | Additional CSS class names applied to the root element. |
data-testid | string | — | Test ID applied to the root element. |
isDefaultOpen | boolean | — | Whether the collapsible is open on initial render. Ignored when isOpen is provided. Defaults to true. |
isDisabled | boolean | — | Whether the trigger is disabled. Prevents toggling and applies disabled styling. |
isOpen | boolean | — | Controls the open state externally. When set, the component becomes controlled and onOpenChange should be provided to handle toggling. |
onOpenChange | (isOpen: boolean) => void | — | Called when the open state changes, either from a user click on the trigger or from a keyboard interaction. |
ref | Ref<HTMLDivElement> | — | Ref forwarded to the root element. |
style | CSSProperties | — | Inline styles applied to the root element. |
trigger* | ReactNode | — | Content 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. |