Experience System

Card

Bordered surface for grouping related content, metadata, and actions.

Installation

The components are exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to build a Card:

Card
├── CardHeader
│   ├── CardTitle
│   ├── CardDescription (optional)
│   └── CardAction (optional)
├── CardContent (optional)
└── CardFooter (optional)

CardHeader uses a @container/card-header grid so CardAction aligns to the top trailing edge when present. CardTitle and CardDescription are plain div elements (not headings by default)—choose heading levels in the surrounding page if needed.

Usage

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@by/experience-system';

Card and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.

<Card className="max-w-sm">
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Supporting text.</CardDescription>
  </CardHeader>
  <CardContent>Body content.</CardContent>
</Card>

Examples

Overview

Basic header and content.

Form layout

CardAction in the header for a secondary control, CardFooter for primary actions.

Elevation

Shadow utilities on Card for depth (shadow-sm through shadow-xl).

Interactive patterns

Link-wrapped card, selectable cards, and RadioGroup card options (focus rings on the interactive wrapper).

Clickable card (link)

Full card is a link: interactive cards have stronger outline than regular cards, use `border-neutral-alpha-6` and `border-neutral-alpha-8` for normal and hovered states respectively.

Selectable cards

Click a card to select it; selection uses a higher elevation than idle cards.
Selectable cards have stronger outline than regular cards, use `border-neutral-alpha-6` and `border-neutral-alpha-8` for normal and hovered states respectively.

Radio cards

Use RadioGroup with Radio at the start of each card; wrap the card in Label so the whole surface toggles the option. Only one card can be selected at a time.

Expanded (full-page)

CardExpandTrigger in CardAction toggles the card between collapsed and expanded states via the expanded / onExpandedChange props on Card.

Wrap expandable cards in ThemeProvider with color="light" or color="dark" so semantic tokens resolve correctly when the card portals to document.body in expanded mode.

Expanded (full-page) with content

CardExpandedContent renders only when the card is expanded. Use it for supplementary content — charts, detailed breakdowns, or any region that should stay hidden until the user expands the card.

The demo uses the registry BarChart recipe (npx shadcn@latest add @by-es/bar-chart) with optionOverrides so chart colors and typography align with design tokens. ECharts cannot read CSS var(--color-*) strings directly — the demo resolves tokens to computed colors at runtime before passing optionOverrides. Apps that install @by-es/bar-chart need the same resolution step (or literal color values) until chart theming is added to the registry recipe.

Chart regionToken / typography
Sales series--color-charts-categorical-1
Forecast series--color-charts-categorical-2
Axis and legend labels--color-neutral-11, fontSize: 14 (text-sm)
Grid / split lines--color-neutral-alpha-6
Tooltip text on tooltip surface--color-neutral-12 on --color-neutral-1

See Core colors for the full chart categorical palette.

API Reference

Subsection titles name the exports from @by/experience-system. These are layout div components (not Radix primitives).

Card

Data attributeValues
data-slotcard
data-expandedPresent when the card is in expanded (full-page) mode
data-stateopen during expand enter animation; closed during collapse fade-out
PropTypeDescription
expandedbooleanControlled full-page expanded state
onExpandedChange(expanded: boolean) => voidCalled when expand state should change

Also accepts standard div attributes.

CardHeader

Data attributeValues
data-slotcard-header

Also accepts standard div attributes.

CardTitle

Data attributeValues
data-slotcard-title

Also accepts standard div attributes.

CardDescription

Data attributeValues
data-slotcard-description

Also accepts standard div attributes.

CardAction

Trailing header actions; participates in the header grid when data-slot="card-action" is present on this node.

Data attributeValues
data-slotcard-action

Also accepts standard div attributes.

CardContent

Data attributeValues
data-slotcard-content

Also accepts standard div attributes.

CardFooter

Data attributeValues
data-slotcard-footer

Also accepts standard div attributes.

CardExpandTrigger

Icon button that toggles the parent Card between collapsed and full-page expanded states. Requires expanded and onExpandedChange on Card.

Data attributeValues
data-slotcard-expand-trigger
PropTypeDefaultDescription
expandLabelstring"Expand card"Accessible label when collapsed
collapseLabelstring"Collapse card"Accessible label when expanded

CardExpandedContent

Content wrapper that mounts only when the parent Card is expanded. Use for charts, tables, or other heavyweight regions that should not render while collapsed.

Data attributeValues
data-slotcard-expanded-content

Also accepts standard div attributes.

Accessibility

Prefer a single primary interactive element per card (one a or button wrapping the card, or explicit buttons inside). If the whole card is clickable, put the focus ring on the wrapper and avoid nested tab stops. For radio-style cards, use RadioGroup + Radio with a Label wrapping the card so the control name matches the visible title. See MDN — Cards for generic container semantics and your product pattern library for card-specific guidance.

Source in the repo: packages/experience-system/src/components/Card/Card.tsx. Agent-oriented contracts: packages/experience-system/src/components/Card/Card.instructions.md.