Gesso
Layout

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 button with aria-expanded, and aria-controls points at the content while it is open. With asChild those 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 through asChild the ring goes on your own element.
  • Closed content gets the hidden attribute, 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

On this page