Components / Card
Card stable
A bordered/elevated content container — the camp card, dashboard tiles, and general grouped content. A surface with a card radius, hairline border, section-scale padding, and optional elevation.
Source: specs/card/spec.json · specs/card/notes.md
Live preview
Rendered from the tier-2 component tokens + foundation tokens per this spec — a spec-faithful rendering, not the production component (those live in each product repo). Variants and states come from the spec below.
Fairhope Soccer Camp
$249
flatFairhope Soccer Camp
$249
raisedFairhope Soccer Camp
$249
interactiveAnatomy
root- The container: surface background, card radius, hairline border, section padding, optional shadow.
media- Optional image/media header (e.g. the camp photo), clipped to the card radius.
body- The content region — title, meta, and an actions row.
Token bindings
Each binding's value is resolved live from the built
@beestera/tokens output — tier-2 component tokens
(--card-*) collapse through the foundation tier.
| Part | Property | Token | Resolved | Applies to |
|---|---|---|---|---|
root | border-radius | --card-radius | 0.75rem --card-radius → --radius-card | — |
root | background | --card-bg | #ffffff --card-bg → --color-surface | — |
root | border-color | --card-border | #e4e4e7 --card-border → --color-border | — |
root | padding | --card-padding | 1.5rem --card-padding → --spacing-section | — |
root | box-shadow | --card-shadow | 0 1px 2px rgba(24, 24, 27, 0.06), 0 1px 3px rgba(24, 24, 27, 0.1) --card-shadow → --shadow-card-default | elevation:raised |
root | box-shadow | --card-shadow | 0 1px 2px rgba(24, 24, 27, 0.06), 0 1px 3px rgba(24, 24, 27, 0.1) --card-shadow → --shadow-card-default | elevation:interactive |
root | box-shadow | --card-shadow-hover | 0 4px 6px rgba(24, 24, 27, 0.08), 0 2px 4px rgba(24, 24, 27, 0.12) --card-shadow-hover → --shadow-card-hover | elevation:interactive,state:hover |
Variants
-
elevationflatraisedinteractive - flat = border only, no shadow; raised = a resting card shadow; interactive = raised that lifts to the hover shadow on hover (for cards that are themselves links/buttons).
State matrix
default- Resting appearance for the active elevation.
hover- Only the interactive elevation reacts — shadow lifts from card-default to card-hover.
Accessibility
- Role
- group / article for content cards; the interactive elevation MUST wrap a real link/button, not attach a click to a div.
- Contrast
- Card content inherits foundation text-on-surface pairs (all AA). The card itself adds no text.
- Focus order
- flat/raised are non-interactive. interactive cards expose one focusable control; the whole-card affordance MUST be keyboard reachable.
- Target size
- For interactive cards, the primary affordance target >= 44px (--spacing-touch-min).
- Don't rely on the hover shadow to convey interactivity — pair it with a real focusable control and a cursor affordance.
- Media headers must keep the card radius (overflow clipped).
Usage
Do
- Use flat for dense grids, raised to separate a card from a busy background, interactive when the whole card is a link.
- Keep the section padding token (--card-padding) rather than ad-hoc padding.
- Clip media to the card radius.
Don't
- Don't hardcode box-shadow or radius literals — bind to --card-*.
- Don't make a non-interactive card look clickable (raised is not interactive).
- Don't nest cards more than one level deep.
Implementations
-
swarm-os -
packages/ui/src/Card.ts—buildCardViewShips elevation: flat|raised|interactive with the exact radius/border/padding/shadow tokens this spec binds.
Figma source: Card