Collapsible
A single expandable section with an animated height transition. Use it to progressively disclose content without navigating away.
Preview
A design system is a collection of reusable components, guidelines, and tokens that teams use to build consistent user interfaces.
Motion
| Property | Enter | Exit |
|---|---|---|
| height | 0 → auto (200ms ease) | auto → 0 (200ms ease) |
| opacity | 0 → 1 (200ms ease) | 1 → 0 (200ms ease) |
| chevron rotate | 0° → 180° | 180° → 0° |
Implementation
Collapsible.tsx
tsx
import { Collapsible } from "@/components/ui/Collapsible";
// Uncontrolled
<Collapsible title="Advanced settings">
<p>These settings affect performance and security.</p>
</Collapsible>
// Open by default
<Collapsible title="Details" defaultOpen>
<p>This section is open by default.</p>
</Collapsible>
// Controlled
const [open, setOpen] = useState(false);
<Collapsible title="More info" open={open} onOpenChange={setOpen}>
<p>Controlled content.</p>
</Collapsible>Props
| Prop | Type | Default | Description |
|---|---|---|---|
title* | React.ReactNode | — | Trigger label rendered in the header button. |
children* | React.ReactNode | — | Content revealed when open. |
defaultOpen | boolean | false | Initial open state for uncontrolled usage. |
open | boolean | — | Controlled open state. Pair with onOpenChange. |
onOpenChange | (open: boolean) => void | — | Called when the open state changes. |
className | string | — | Additional classes on the root element. |
contentClassName | string | — | Additional classes on the content wrapper. |
ARIA roles
| Element | Role | Key attributes |
|---|---|---|
| Trigger | button (implicit) | aria-expanded (true/false), aria-controls |
| Content | region | aria-labelledby (trigger id) |
Keyboard
| Key | Action |
|---|---|
| Enter / Space | Toggle open or closed when trigger is focused |
| Tab | Move focus into the content area (first interactive element) when open |
| Shift+Tab | Return focus to the trigger from the content area |
Accessibility
- →The trigger button uses aria-expanded to communicate open/closed state to screen readers.
- →The content region is linked via aria-controls — screen readers can jump directly to the content.
- →Content uses role="region" so it appears as a landmark when open.
- →Keyboard: Enter or Space activates the trigger; Tab moves focus into the content when open.
- →The animated height uses framer-motion with AnimatePresence — content is truly removed from the DOM when closed, not just hidden.