Area Chart
Token-driven area chart built on Recharts v3 with gradient fills, stacking, single and multi-series support, and full light/dark theming via chart tokens.
Playground
Installation
pnpm add @tessinaui/ui rechartsUsage
import { AreaChart } from "@tessinaui/ui";
import type { ChartConfig } from "@tessinaui/ui";
const config: ChartConfig = {
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" },
};
const data = [
{ month: "Jan", desktop: 186, mobile: 80 },
{ month: "Feb", desktop: 305, mobile: 200 },
{ month: "Mar", desktop: 237, mobile: 120 },
];
<AreaChart data={data} config={config} categoryKey="month" />Series colours come from the --chart-1 … --chart-6 tokens, referenced as
var(--chart-N) in each config entry. They are brand-led, contrast-checked
in both modes, and recolour automatically when the theme flips — no JS re-render.
Examples
Default
A single series with the softest natural curve and a brand-tone gradient fill.
Multiple Series
Two series in brand tones with a legend; the areas overlap rather than stack.
Stacked
Two series stacked into a cumulative area, with each solid contour separating the bands.
Expanded
Three series normalised to 0–100% proportional bands with expand.
Curve
linear joins points with straight segments; step holds each value until the next.
Axes
Both axes labelled, values formatted, and the chart given an accessible name and summary.
Reference lines and bands
Recharts children compose straight through: a shaded band for the weekend, a dashed goal line with its own label.
Range band
A [low, high] pair plots a band between two bounds — a normal range, a forecast envelope — with the actual reading drawn over it.
Missing data
A null leaves a gap. connectNulls bridges it with a straight segment interpolated between the surrounding readings — a value nobody measured, drawn in the same stroke as the data. Only correct when a gap genuinely means "unchanged".
Negative values
Values crossing zero, with a reference line marking the baseline.
Sparklines
Every piece of chrome turned off, so the chart reads as a trend line inside a stat card.
Interactive
A time-range control filtering the data, with a running total above the plot.
States
The loading skeleton and the empty state shown side by side.
Theming
Every colour resolves to a Tessina token — nothing is hardcoded:
| Element | Token |
|---|---|
| Series fill / stroke | --chart-1 … --chart-6 (via config[key].color) |
| Grid + axis lines | border |
| Axis tick labels | muted-foreground |
| Tooltip surface | popover / popover-foreground / border |
To re-skin a series, change the token reference in config (e.g. color: "var(--chart-3)")
or override the --chart-N token in your theme.
Accessibility
- Name the insight, not the axes.
labelrenders the chart as a namedrole="figure";descriptionadds a visually hidden summary. A static read has to carry the message — interaction is additive, never required. - Keyboard. Recharts'
accessibilityLayeris on by default: the chart surface is a tab stop,←/→step through data points,Enterpins the tooltip andEscdismisses it. There is no wrapping and noHome/End. The focus ring is drawn byChartContainer— Recharts ships none. See the Chart page for the full keyboard table and the VoiceOver QuickNav caveat. - Gaps are never interpolated.
connectNullsis off by default: a missing value stays a gap rather than being bridged. Turn it on only when a gap genuinely means "unchanged". - Colour is never the only differentiator. Pair multi-series with
dashedSeries, dots or direct labels, and keep to about five series — past that a stacked area stops being readable and a data table serves better.
Core props
| Prop | Type | Default | Description |
|---|---|---|---|
data | AreaChartDatum[] | — | Row-per-category data. A value may be a [low, high] pair for a banded area. |
config | ChartConfig | — | Series metadata (label, color, icon). |
categoryKey | string | — | Datum key for the x-axis. |
series | string[] | all config keys | Which series to plot. |
curve | "monotone" | "natural" | "linear" | "step" | "monotone" | Interpolation. |
stacked | boolean | false | Stack series cumulatively. |
expand | boolean | false | Normalise the stack to 0–100% bands. Implies stacked. |
fill | "gradient" | "solid" | "gradient" | Fade toward the baseline, or a flat wash. |
fillOpacity | number | 0.25 | Top stop opacity of the fill. |
connectNulls | boolean | false | Bridge null gaps with an interpolated segment. |
size | "sm" | "md" | "lg" | "md" | Aspect ratio. |
showGrid / showXAxis / showLegend / showTooltip | boolean | true | Toggle chart chrome. |
showYAxis / showDots | boolean | false | Off by default. |
xAxisLabel / yAxisLabel | string | — | Axis titles. |
xTickFormatter / yTickFormatter | (value, index) => string | — | Format tick text. |
startEndOnly | boolean | false | Render only the first and last category ticks. |
dashedSeries | string[] | [] | Series keys rendered with a dashed stroke. |
label | string | — | Accessible name; renders the chart as a role="figure". |
description | string | — | Visually hidden summary for assistive tech. |
children | ReactNode | — | Extra Recharts children — ReferenceLine, ReferenceArea, Brush. |
dir | "ltr" | "rtl" | "ltr" | Reading direction. |
loading | boolean | false | Render the skeleton. |
error | ReactNode | — | Render an error message instead of the chart. |
emptyState | ReactNode | "No data to display" | Shown when data is empty. |
initialDimension / debounce | — | {320,200} | Pre-measurement size and resize debounce, forwarded to Recharts. |