Expandable List
betaA list of compact rows — jobs, releases, people — each growing into a detail dialog out of its place in the list.
- Display
- 1 file
- 3 npm packages
- Accessibility notes
- Animates with motion
Loading the example…
pnpm dlx @dowel-ui/cli add expandable-listnpm packages installed: class-variance-authority, motion, radix-ui.
import { ExpandableList } from "@/components/ui/expandable-list";Default
Gallery
Compact
Without Media
<ExpandableList>
| Prop | Type | Default |
|---|---|---|
items (required) | ExpandableListItem[] | — |
closeLabelAccessible name of the Close button. | string | "Close" |
defaultValue | string | null | null |
density | "comfortable" | "compact" | "comfortable" |
onValueChange | (value: string | null) => void | — |
rowClassName | string | — |
valueThe open item's id, or null (controlled). | string | null | — |
Plus every attribute of <ul> except children, defaultValue.
Each row is a button with aria-haspopup="dialog" and aria-expanded. The detail is a Radix modal dialog: focus moves in and is trapped, the page behind is inert, it is named by the item's title and described by its content, and Escape, the backdrop or the named Close button dismiss it — after which focus returns to the row that opened it. Row media is aria-hidden. The grow-from-row animation uses motion's layoutId inside MotionConfig reducedMotion="user", so under reduced motion the dialog appears in place.
8/8
checks passed
- Tested — passes
- axe assertion — passes
- Keyboard tested — does not apply
- Storybook examples — passes
- Accessibility documented — passes
- Semantic tokens only — passes
- Motion from tokens — passes
- className merged — passes
- Visible focus — does not apply
- No fixed widths — passes