skrewww

Content & Data

Area Chart

Area Chart shows magnitude over ordered categories with a filled area under each line — a single series by default, or several series overlapped, stacked, or stacked to 100%. Static and non-tabbable, with the data also exposed as a hidden table.

Single component — stacking is a prop, not a variant

betaReact AvailableFigma UnavailableDocs Partial
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

  • There is no Figma reference for Area Chart: nothing here is Figma-verified and no Figma parity is claimed. Fill opacity (0.2), stroke width (2px) and the default category axis are conservative implementation choices using current Skrewww tokens, and need a design decision.
  • The categorical series palette (four slots: brand action, primary text, amber, green) is a CH-2 implementation choice checked for >= 3:1 contrast on the default white surface, not a Figma-verified palette. Slots carry no status meaning; positive/negative colors are a separate, unresolved design decision.
  • An interactive legend (series toggling) is deferred: it needs a hidden-series state plus keyboard and announcement design. The legend is a non-interactive key.
  • The tooltip is pointer/touch-only supplementary information; every value is also in the hidden table. A keyboard-operable chart (focusable points, arrow-key navigation) is deferred.
  • Dense data and long category labels have no policy yet: category labels always render (no tick skipping, rotation, truncation, scrolling or aggregation), so many points or long labels can collide or clip. A density and label-length policy is an open decision.
  • Stacked area with negative values is not designed; use non-negative data for stacked and percent modes.

Purpose

Show magnitude or accumulation over ordered categories with a filled area: a single series by default, or several series overlapped, stacked, or stacked to 100%.

Anatomy

A recharts AreaChart inside ResponsiveContainer (fluid width, fixed height). Each series is an Area: a 2px linear stroke over a translucent fill (opacity 0.2) in its series color; slot 1 is semantic/action/primary. Category labels render on the category axis in semantic/text/secondary; a value axis, grid, legend and tooltip are opt-in. stacking="none" overlaps series, "stacked" stacks them, and "percent" stacks to 100% of each category. A missing value (null) leaves a gap. A visually-hidden (`sr-only`) data table — one column per series — is rendered alongside, and the visual is aria-hidden inside a role="img" wrapper with aria-label + aria-describedby pointing at the table.

Variants and states

default

When to use

Volume over time, cumulative totals, or the composition of a whole across ordered categories (stacked / percent) where the filled area helps the reader.

When not to use

Only the shape of change matters, or many overlapping series — use Line Chart. Comparing discrete categories — use Bar Chart. Data with negative values in stacked or percent mode (not designed). Parts of a single whole (Pie/Donut do not exist yet).

Accessibility

role="img" with an accessible name, described by a visually-hidden (sr-only) data table with one column per series (missing values read "No data"), so series are identified by name, never by color alone. The plot, legend and tooltip are aria-hidden; the chart is a static visualization with no keyboard tab stop, and the tooltip is pointer/touch-only supplementary information.

Common mistakes

Using a `stacked` boolean or a separate StackedAreaChart — use stacking="stacked" | "percent". There is no Figma reference for Area Chart, so do not claim Figma parity. Inventing props: chart families are separate components (do not add a `type`, `variant` or `stacked` prop), `series` keys must be unique and never "label", and raw Recharts props are not accepted. Pie, Donut, Scatter, Heatmap, Waterfall, Funnel and other chart families do not exist yet — do not import or claim them. Treating "static" as "no accessibility work needed": the hidden data table is required, not optional polish.

Properties

data (rows: { label, value } or { label, [series.key]: number | null }). label (accessible name / hidden table caption). height (fixed pixel height, default 240 — width is fluid, filling the parent). stacking ("none" overlapped, default | "stacked" | "percent"). series ({ key, label, color?, format? }[] — omit for a single series read from each row's `value`), legend (boolean, default: true for several series), tooltip (boolean, default false), showCategoryAxis, showValueAxis, showGrid (booleans), valueFormat (a function or { kind: "number" | "compact" | "percent" | "currency" }), labelFormat (a function or { kind: "date", granularity }).

Area Chart or Line Chart?

Use Area Chart when magnitude or accumulation matters (the fill reads as volume, and stacked/percent modes show composition over time). Use Line Chart when only the shape of change matters, especially with several overlapping series.

Why is there no separate StackedAreaChart?

Stacking is a mode: stacking="stacked" or "percent". Do not invent a `stacked` boolean, a `type` prop, or a separate component.

How is the underlying data exposed to screen readers?

A visually-hidden (sr-only) table with a column per series, linked to the chart via aria-describedby — the same mechanism every Cartesian chart uses. Fill and color alone convey nothing to assistive tech.

Why do overlapped series use translucent fills?

So a series drawn behind another stays visible. With many series, overlapping fills get hard to read; prefer stacking or Line Chart.

Tokens used

semantic/action/primarysemantic/text/primarysemantic/text/secondarysemantic/border/defaultsemantic/border/strongsemantic/surface/defaultsemantic/surface/subtlecolor/warning/700color/success/700

Component API

PropTypeDefaultDescription
data({ label: string } & { [seriesKey: string]: string | number | null })[]One row per category: { label, value } for a single series, or { label, [series.key]: number | null } when `series` is given. null means no data (a gap or no bar). Duplicate labels are allowed.
labelstringAccessible name for the chart — also used as the hidden data table's caption.
heightnumber240Fixed pixel height. Width is fluid (ResponsiveContainer), filling the parent.
series{ key: string; label: string; color?: "series-1" | "series-2" | "series-3" | "series-4"; format?: ChartValueFormat }[]Series definitions: { key, label, color?, format? }. `key` is the row property holding the value (unique, never "label"); `color` is a slot "series-1"…"series-4" (default: by position, wrapping after four). Omit for a single series read from each row's `value`.
stacking"none" | "stacked" | "percent""none""none" overlaps series; "stacked" stacks them; "percent" stacks to 100% of each category.
legendbooleanShow a legend. Defaults to true when there is more than one series. It is a non-interactive key (no series toggling).
tooltipbooleanShow a hover/touch tooltip listing the category and each series' value. Defaults to false. Supplementary only: every value is also in the hidden table.
showCategoryAxisbooleanShow the category axis labels. Defaults to true.
showValueAxisbooleanShow the value-axis ticks. Defaults to false.
showGridbooleanShow grid lines. Defaults to false.
valueFormatChartValueFormatHow values are formatted on value-axis ticks, in the tooltip, and in the hidden table: a function (value: number) => string, or { kind: "number" | "compact" | "percent" | "currency", ... }. Default: the raw number. A series' own `format` overrides it.
labelFormatChartLabelFormatHow category labels are formatted on the axis, in the tooltip, and in the hidden table: a function (label: string) => string, or { kind: "date", granularity: "day" | "month" | "year" } for normalized ISO date labels (UTC). Default: the label as given.

React example

Copy React example
import { AreaChart } from "@/components/ui";

const monthlyUsers = [
  { label: "Jan", value: 120 },
  { label: "Feb", value: 180 },
  { label: "Mar", value: 150 },
  { label: "Apr", value: 240 },
  { label: "May", value: 210 },
  { label: "Jun", value: 300 },
];

const traffic = [
  { label: "Mon", web: 320, mobile: 210 },
  { label: "Tue", web: 360, mobile: 240 },
  { label: "Wed", web: 300, mobile: 280 },
  { label: "Thu", web: 410, mobile: 260 },
];

export function Example() {
  return <AreaChart data={monthlyUsers} label="Monthly active users" />;
}

export function MultiSeriesExample() {
  return (
    <AreaChart
      data={traffic}
      label="Traffic by platform"
      series={[
        { key: "web", label: "Web" },
        { key: "mobile", label: "Mobile" },
      ]}
      stacking="stacked"
      showValueAxis
      showGrid
      tooltip
      valueFormat={{ kind: "compact" }}
    />
  );
}