Expandable Cards
betaCards that open into a detail view — widening in place in a row, or growing out of a grid into a modal.
- Display
- 1 file
- 2 npm packages
- Accessibility notes
- Animates with motion
Loading the example…
pnpm dlx @dowel-ui/cli add expandable-cardsnpm packages installed: motion, radix-ui.
import { ExpandableCards } from "@/components/ui/expandable-cards";Default
Gallery
Controlled
As Dialog
<ExpandableCards>
| Prop | Type | Default |
|---|---|---|
items (required) | ExpandableCardItem[] | — |
cardClassNameClasses for every card. | string | — |
defaultValueThe open card's id at first (uncontrolled). | string | null | null |
onValueChange | (value: string | null) => void | — |
presentation
| "inline" | "dialog" | "inline" |
valueThe open card's id, or null (controlled). | string | null | — |
Plus every attribute of <div> except children, defaultValue.
Every card is a real button. Inline, it is a disclosure (aria-expanded, aria-controls) whose detail is a labelled region: opening moves focus into it, Escape closes it from anywhere inside the card and returns focus to the card, and a closed detail is aria-hidden and inert. As a dialog it is a Radix modal — focus trap, aria-modal, title and description, Escape and a named Close button — and focus returns to the card that opened it. Motion follows MotionConfig reducedMotion="user": under reduced motion the card still opens, without growing out of its place.
10/10
checks passed
- Tested — passes
- axe assertion — passes
- Keyboard tested — passes
- Storybook examples — passes
- Accessibility documented — passes
- Semantic tokens only — passes
- Motion from tokens — passes
- className merged — passes
- Visible focus — passes
- No fixed widths — passes