ExpandableCard
A bento card that opens into a dialog by turning into it, its title travelling along, and closes back into the card.
import { ExpandableCard } from "@butteryui/react";Demo
Tilt it in 3D
Product shot
Any size
16:9
When to use it
Use it for
- A feature card with more to say than fits: a short video, steps, a longer note
Not for
- A card that links to its own page: put a link in a Bento.Card
- A decision or a form: use Dialog
Examples
Card variants
A tinted card beside a plain one, each opening into the same dialog surface.
Gift a subscription
3 months
Brew guides inside
Pour-over · 1:16
Controlled card
A card opened from elsewhere on the page too, with open and onOpenChange kept in useState.
Any size
16:9
Every change kept
Undo · Redo
Feature detail
A feature card with a longer note behind it, beside a card that doesn't open.
Every change kept
Undo · Redo
Any size
16:9
Features section
A features section whose large card opens into a dialog with a setting to try, beside a plain card.
Made to be presented
Tilt it in 3D
Product shot
Any size
16:9
Variants and states
Cards that open
The card grows into the dialog on spring.morph with its title travelling along; the others step back. Closing turns it back into the card on spring.smooth.
Tilt it in 3D
Product shot
Any size
1:1
16:9
Variants
Each card draws itself as a Bento.Card does: outline, soft, tinted or plain, beside elevated above. Open, every one is a dialog on the dialog surface.
Every change kept
outline
Notes for the room
soft
A background for every talk
tinted
Present from any screen
plain
Props
ExpandableCard
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | The card's title. It travels into the dialog and names it. | |
detailrequired | ReactNode | What the open card shows under its title. | |
span | 4 | 6 | 8 | 12 | 6 | Columns it spans in a Bento once the grid is 1024px wide. Defaults to 6. |
rows | 1 | 2 | 1 | Rows it spans in a Bento. Defaults to 1. |
variant | "elevated" | "outline" | "soft" | "plain" | "tinted" | "elevated" | How the card draws its surface in the grid, as a Bento.Card does: "elevated" (the default),
"outline", "soft", "plain" or "tinted". Open, it's a dialog on the dialog surface whatever its
variant, since a dialog over the scrim needs one. |
open | boolean | Whether it's open. Pair with onOpenChange to control it. | |
defaultOpen | boolean | false | Whether it starts open when uncontrolled. Defaults to false. |
onOpenChange | ((open: boolean) => void) | Called when it opens or closes. | |
openLabel | string | Names the button that opens it. Defaults to "Open" and the title. | |
closeLabel | string | "Close" | Names the button that closes it. Defaults to "Close". |
It also takes Omit<MotionElementProps<"article">, "title">.
Accessibility
The card is an article named by its title; its expand button ("Open" and the title) opens a modal dialog named by the same title. Escape, the close button or a press outside closes it, and focus goes back to the expand button. With reduced motion the dialog fades in and out instead of growing from the card.
| Keys | What they do |
|---|---|
| Enter or Space | Opens the card into its dialog from the expand button |
| Tab or Shift+Tab | Moves through what's in the open dialog and its close button, staying inside |
| Escape | Closes the dialog back into the card, with focus on the expand button |
Motion
It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.
With reduced motion: The dialog fades in and out in place in 100ms instead of growing out of the card, the title doesn't travel, and the other cards fade back in 100ms.
Layout
In a Bento it spans 4, 6, 8 or 12 of 12 columns and one or two 320px rows (--bt-bento-row) like any card, with a 28px expand button at its top end; the whole card is the press target. In the grid it draws itself as a Bento.Card does, elevated (the default), outline, soft, tinted or plain. Open, it's a dialog on the dialog surface whatever its variant, at most 880px wide (--bt-w-dialog-lg) with the card's radius, 16px inside the window, scrolling inside when its detail is taller.