Collapsible
Show and hide a block. Unstyled trigger; you bring the control.
Collapsible shows and hides one block of content. The root and the trigger are unstyled, so
you compose the control from a Button or any element with
asChild, and the content animates its height as it opens and closes. It is the single
version of Accordion, which stacks rows that share a
look and manage which is open.
Advanced
Usage
import { Button } from '@/components/ui/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
<Collapsible>
<CollapsibleTrigger asChild>
<Button>Toggle</Button>
</CollapsibleTrigger>
<CollapsibleContent>Hidden until open.</CollapsibleContent>
</Collapsible>It starts closed. Set defaultOpen to start open, or control it with open and
onOpenChange, as the demo above does to change the label on its button.
Styling the open state
The trigger and the content carry data-state="open" or "closed", so style the control
from them with a variant such as [&[data-state=open]>svg]:rotate-180. Nothing in
Collapsible draws a caret or a rule, so what the trigger looks like is up to you.
Open from the start, with the caret turned by the trigger's state.
Height and content
CollapsibleContent clips with overflow-hidden and animates its height over
--duration-base with --ease-out. Put spacing on a child, as the demos do with pt-3,
because padding on the content element itself would hold it open at the padding's height
while it closes. Content that starts open does not animate on mount. Closed content is not
rendered, so it is not in the page until the block opens, and forceMount on the content
keeps it mounted. Under reduced motion the block opens and closes without animating.
Props
Collapsible takes every prop of the Radix Collapsible, such as open, defaultOpen,
onOpenChange, and disabled on the root, asChild on the trigger, and forceMount on the
content. It adds no props of its own, and className on the content is merged with its
clipping and animation classes.
Accessibility
- The trigger is a
buttonwitharia-expanded, andaria-controlspoints at the content while it is open. WithasChildthose attributes go onto your element, so use a real button or give a custom element the role itself. - Tab reaches the trigger and Space or Enter toggles the block. There are no arrow keys, because there is only one control.
- The trigger shows the kit's 2px ring on
:focus-visible, and throughasChildthe ring goes on your own element. - Closed content gets the
hiddenattribute, so it is out of the accessibility tree and the tab order. - Focus stays on the trigger when the block opens or closes.
- Under reduced motion the content appears and disappears without animating.
When to use something else
- Accordion for a list of rows that each open and close, where the library should keep one open or arrow between triggers.
- Tabs when the reader picks one of several panels and should see every label.
- Section when the block is always visible and only needs a title and header actions.
Install
shadcn add @gesso/collapsible