Card
Draws one frame around everything about one topic, and can fold its body away.
Examples
Refund policy eval
RunRunning
Scoring support-agent on 142 of 200 tickets.
Spend this period
Resets on Oct 1
$1,284 of $2,000
{
"status": "open",
"since": "24h",
"limit": 50
}Sizes
- TicketsLast 24 hours112 resolved, 9 escalated.
sm
- TicketsLast 24 hours112 resolved, 9 escalated.
defaultdefault
- TicketsLast 24 hours112 resolved, 9 escalated.
lg
Collapsible
- TicketsLast 24 hours112 resolved, 9 escalated.
falsedefault
{ "status": "open", "since": "24h", "limit": 50 }true
States
- 3 arguments
folded
- 3 arguments
open
- …
hover
- …
focus
- …
disabled
Usage
When to use it
- Use
- A page has several topics and each needs its own box: spend, usage, invoices.
- Use
- Detail most people skip but some need: a tool call's input, a trace step's attributes (collapsible).
- Use
- A settings page groups fields; fold the groups so the page fits and keep them deep-linkable.
- Not for
- A list of similar things, one per row. Use Item.
- Not for
- Show and hide one region with no box around it. Use Collapsible.
- Not for
- A message about trouble that the person must see. Use Alert.
- Not for
- A summary or a decision: a few peer rows, one chart, a short read-only fact set, a card that asks Resume or Roll back. Use Panel.
Rules
10 rules
- Must
- Use a Card for a working surface: a big table or stream, a trace, a log, code, a form or settings group with fields the person edits, the detail of one object; anything that scrolls, filters, streams or is edited. Use a Panel for a summary or a decision. Because a working surface needs the white frame's edge and the rule under its header to show where it ends, while a glanceable widget reads best as a quiet grey group.
- Never
- A widget is a Panel and a working surface is a Card; never a Card inside a Panel or a Panel inside a Card. Because a white card in a grey panel is a box in a box; side by side, the grey widget and the white table tell the person which one to glance at and which one to work in.
- Never
- Put a Card inside a Card; a card is one frame, and rows inside it are split by rule-row, never by a second box. Because a box in a box stacks two edges until no one can tell which group a line closes.
- Must
- Put actions in CardAction beside CardTrigger, never inside it. Because CardTrigger is a button, and a button inside it is invalid HTML that also folds the card on every press.
- Must
- Add a CardMeta that says what is inside to every collapsible card, such as "3 arguments". Because folded, the header is all that shows, and a title alone does not say whether the body is worth opening.
- Should
- Name the topic in 1 to 4 words, sentence case. Because a collapsible card truncates its title to one line.
- Never
- Add a border or shadow to a Card. Because the card already draws its one edge, and a shadow makes it read as an overlay above the page.
- Must
- Give a card that holds a form or group of controls role="group" and aria-labelledby its title. Because Card renders a plain div, so a screen reader tabbing into its fields never hears what they belong to.
- Should
- Link to a page instead of folding a body taller than 480px open. Because a tall body pushes the cards below it off the screen when it opens.
- Never
- Put a button inside a Card rendered as a link. Because a button inside a link is invalid HTML, and a press on it also follows the link.
Accessibility
5 checks
- A collapsible trigger is a button with aria-expanded and aria-controls, named by title and meta.
- A folded body uses hidden="until-found", so find-in-page and fragment links open it.
- Enter or Space toggles the trigger; Tab reaches the trigger, then each action.
- Use role="group" with aria-labelledby on a card that holds controls.
- Disabled trigger is keyed off aria-disabled; search still opens the body.
Edge cases
11 edge cases
- Very long title
- Wraps in a static card; truncates in a collapsible header and keeps the caret and actions.
- One-word title
- The header keeps 40px.
- Empty body
- Disable the trigger when there is nothing to show.
- Wide child
- overflow-hidden clips it; a table inside scrolls itself.
- Right to left
- The caret and the actions move to the left edge.
- Zoom 200%
- The header grows with the text; the body follows its content.
- Toggled ten times in a second
- Ends in the last state; the body cuts, so nothing lags.
- Inside a form
- The trigger is type="button" and never submits.
- Request failed inside
- Show an Alert inside; the card does not change.
- Forced colors
- The frame line survives as CanvasText; the kind badge keeps its text.
- Reduced motion
- Nothing changes: the body already cuts.
API
5 props
Card size'sm' | 'default' | 'lg'default'default'Card kindKindCard collapsiblebooleandefaultfalseCard renderReactElementCardTrigger disabledbooleandefaultfalse
Tokens
16 tokens
--surface-page- var(--paper)
--line-frame- oklch(var(--ink-c) / var(--line-frame-alpha))
--line-row- oklch(var(--ink-c) / 0.08)
--accent-mark- var(--ink)
--radius-frame- 10px
--space-3- 12px
--space-4- 16px
--space-6- 24px
--surface-hover- oklch(var(--ink-c) / 0.05)
--line-focus- 2px
--dur-fast- 120ms
--ease-step- cubic-bezier(0.2, 0, 0, 1)
--type-label- 400 14px/16px var(--font-sans)
--type-label-sm- 400 12px/16px var(--font-sans)
--type-body-sm- 380 13px/18px var(--font-sans)
--text-3- oklch(var(--ink-c) / 0.66)