Containers & Overlays
Chart Card
Chart Card is a Card composed for a chart: an optional title/description/actions header, and a body that owns loading/empty/error presentation around a chart the consumer supplies as children.
Single component — state is a prop, not a variant
- React last updated
- 2026-09-22
- Documentation last updated
- 2026-09-22
- Accessibility target
- WCAG 2.2 AA (target)
- Version
- 0.1.0-beta
Known open questions
- No Figma reference exists for Chart Card or its states — this implementation uses Card's own established visual language (spacing, type scale) conservatively; no Figma parity is claimed.
- A canonical, generic time-range control (e.g. a shared TimeRangeTabs component) was evaluated and rejected for v1: Banking Balance Summary already proves the composition (Tabs in the `actions` slot, one TabsPanel per range, each holding its own chart instance) without a new abstraction. Compose with Tabs/ToggleGroup/Select/ButtonGroup directly; promote to a shared control only if a second, materially different real use case emerges.
- Interactive legend (per-series toggling) remains out of scope, unchanged from CH-2 — Chart Card does not add one.
- `errorAction`/`emptyDescription` accept arbitrary ReactNode but Chart Card fetches nothing itself — retry/action behavior is entirely the consumer's.
- Chart Card was not retrofitted onto Banking Account Card or Banking Balance Summary — both predate it and remain unchanged; their duplicated metric-value styling (documented on Chart Metric) is left as recorded evidence, not migrated, since neither is trivial/zero-risk to change without its own review.
Purpose
Anatomy
Variants and states
When to use
When not to use
Accessibility
Common mistakes
Properties
Why doesn't Chart Card have its own Shape/Surface props?
Why is there no `metric` prop?
Why does loading/empty/error hide `children` instead of layering on top?
Why is `contentHeight` a `minHeight`, not a fixed height?
Tokens used
Component API
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | — | Optional header title. |
| description | ReactNode | — | Optional header description, shown under the title. |
| headingLevel | "h2" | "h3" | "h4" | "h3" | Heading level for the optional title. |
| actions | ReactNode | — | Header-right slot — e.g. a time-range Tabs group, a filter Button, or a Menu of chart actions. |
| elevation | "flat" | "raised" | — | Passed through to the underlying Card. |
| state | "ready" | "loading" | "empty" | "error" | "ready" | Which region renders in the body. |
| contentHeight | number | 240 | Minimum body height in pixels, matching a chart's own default height — keeps the card's size stable across every state. |
| loadingLabel | string | "Loading chart" | Visually-hidden label announced while state is "loading". |
| emptyTitle | string | "No data" | EmptyState title while state is "empty". |
| emptyDescription | ReactNode | — | EmptyState description while state is "empty". |
| errorTitle | string | "Couldn't load chart" | Alert title while state is "error". |
| errorDescription | ReactNode | — | Alert description while state is "error". |
| errorAction | ReactNode | — | e.g. a "Retry" Button, rendered alongside the error message. |
| children | ReactNode | — | Rendered only when state is "ready" — typically a Chart Metric and a chart. |
| footer | ReactNode | — | Passed through to the underlying Card's footer. |
React example
import { ChartCard, ChartMetric, LineChart } from "@/components/ui";
const trend = [
{ label: "Jan", value: 58 },
{ label: "Feb", value: 95 },
{ label: "Mar", value: 76 },
{ label: "Apr", value: 128 },
];
export function Example() {
return (
<ChartCard title="Monthly signups" description="Last 4 months">
<ChartMetric label="Total" value="357" delta={{ direction: "up", value: "+12%", label: "vs prior period" }} />
<LineChart data={trend} label="Monthly signups" showCategoryAxis tooltip />
</ChartCard>
);
}
export function LoadingExample() {
return <ChartCard title="Monthly signups" state="loading" />;
}
export function EmptyExample() {
return (
<ChartCard title="Monthly signups" state="empty" emptyDescription="No signups recorded for this period." />
);
}
export function ErrorExample() {
return (
<ChartCard title="Monthly signups" state="error" errorDescription="Something went wrong loading this chart." />
);
}