Skip to content

Chart

Token-themed chart primitives (container, config, tooltip, legend, data-table) composed with Recharts.

Stablev0.4.2added in v0.2.0@garn/ui/chart
On this page

Default

Area chart with accessible name, gradient fills, tooltip, legend + a ChartDataTable.

Bar

Stacked bars with rounded tops.

Line

Multi-series line with active dots.

Pie

Donut with a token-tinted slice palette.

Interactive Legend

Click the legend to toggle series (useChartVisibility).

Reference Line

A dashed target line via ChartReferenceLine.

States

Loading (ChartSkeleton) + empty (Empty).

Formatting

Intl date/compact-currency axes + data labels.

Combo

Dual-axis composed bar + line.

Scatter

Two numeric axes; correlation.

Radar

Multi-axis comparison of two series.

Radial Bar

Concentric progress rings.

Funnel

Conversion funnel with stage labels.

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add chart
First time? Set up garn in your project
Registry
chart
Deps
recharts
Registry deps
utilsuse-prefers-reduced-motionskeleton

Import

import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartDataTable, ChartReferenceLine, ChartReferenceArea, ChartLabelList, ChartGradient, ChartSkeleton, ChartStyle } from "@garn/ui/chart";

Anatomy

The parts this component is built from — compose them to assemble it.

ChartContainerRequired[data-slot="chart"]

Root; provides the config context + theming. Takes config and a single chart child. Defaults isAnimationActive onto the series inside (animate prop, off under prefers-reduced-motion); a series' own explicit value wins, and custom series read useChart().animate.

ChartTooltip

Interaction primitive (the library Tooltip), re-exported.

ChartTooltipContent[data-slot="chart-tooltip-content"]

Token-styled tooltip card: label + per-series swatch/value; indicator, hideLabel, nameKey, config-driven formatter.

ChartLegend

Interaction primitive (the library Legend), re-exported.

ChartLegendContent[data-slot="chart-legend-content"]

Token-styled legend; hidden + onItemClick make it an interactive show/hide toggle (consumer owns the state).

ChartDataTable[data-slot="chart-data-table"]

The a11y data-table equivalent, generated from config + data (Intl numberFormat, collapsible).

ChartReferenceLine

Target/threshold line (aliased so the library detects it by type); token-styled by the container.

ChartReferenceArea

Shaded reference band (aliased).

ChartLabelList

Data labels on marks (aliased); label text is token-styled.

ChartGradient

A subtle same-hue fill gradient for areas/bars; put in <defs> with a unique id.

ChartSkeleton[data-slot="chart-skeleton"]

A chart-shaped loading placeholder (composes Skeleton).

ChartStyle

Internal — emits the scoped --color-<series> custom properties from config.

Props

The component's public props and their types.

animate
Description
Entrance/update animation for the series inside. The container defaults `isAnimationActive` onto the library's series (and tooltip) so animation turns off automatically under `prefers-reduced-motion`; set `false` to force a static chart (screenshots, print). A series' own explicit `isAnimationActive` always wins over this default.
Type
boolean
Default
true
childrenRequired
Description
A single Recharts chart element (e.g. `<LineChart>…</LineChart>`).
Type
React.ReactNode
configRequired
Description
Series-key → label + token color map. Drives tooltip/legend + `--color-*`.
Type
ChartConfig

Plus 279 inherited native <div> attributes.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="chart"]

When to use

Build a real chart with axes, tooltip, and legend — line, area, bar, pie, scatter, radar, radial, combo, funnel.

Reach for something else when

  • An inline trend with no chrome (use Sparkline)
  • a single value vs a target (use Gauge or Bullet)
  • a ranked list (use BarList).

Overview

Chart is not one chart — it's the themed primitives every chart is built from: a ChartContainer that turns a typed ChartConfig into scoped --color-<series> tokens and token-styles the SVG, plus a garn-styled tooltip, legend, data-table, reference lines, gradients, and loading/empty states. You compose these with the underlying library's chart elements; each chart type — line, area, bar, pie, scatter, radar, radial, combo, funnel — is a recipe, not a bespoke component. Recharts is the engine, added only when you copy a chart in (the sparkline and gauge stay engine-free).

Guidelines

Compose, don't configure. garn ships primitives + recipes, not a <LineChart data={} /> engine — you write the chart body in JSX and keep full control (mirrors the table decision). Copy a recipe and swap the data.

Theme through config. Point each series' color at a --garn-chart-* token; the container injects var(--color-<series>), so dark mode and re-theming come for free. Never hardcode a hex.

Own the accessibility. A chart needs an accessible name and a text/data-table equivalent (ChartDataTable gives you one for free), plus accessibilityLayer for keyboard + tooltip-on-focus. Distinguish series with more than colour.

Format for humans. Use numberFormatter/dateFormatter for axis ticks and tooltips, and a per-series config.formatter so values read as currency/percent/dates.

Let the container own motion. The entrance animation is JS-driven, so CSS motion-reduce can't stop it — ChartContainer does: it defaults isAnimationActive onto the series and turns animation off under prefers-reduced-motion. Set animate={false} for a static chart (screenshots, print, visual-regression suites); a per-series isAnimationActive overrides the default either way, and custom series read useChart().animate.

Best practices

Do
  • Drive series colours from configvar(--color-<series>); enable accessibilityLayer.
  • Pair every chart with an accessible name + ChartDataTable.
Don't
  • Don't hardcode colours or reach past the primitives into raw library styling.
  • Don't ship a chart with no accessible name or data-table equivalent.
  • Don't hand-wire isAnimationActive per series just to respect reduced motion — the container already does; reserve the per-series prop for real exceptions (e.g. the polar 0×0 gotcha).

Troubleshooting

Pie / radial / radar render nothing.

Cause. A Recharts v3 polar chart inside a responsive container that briefly measured 0×0 gets stuck.

Fix. Set isAnimationActive={false} on the Pie/RadialBar/Radar.

Tooltip/legend content is blank in tests.

Cause. The library reports 0×0 in headless/SSR test environments (no layout), so no payloads reach the content slots.

Fix. Verify those in the browser/lab; unit-test the engine-free parts (ChartDataTable, ChartStyle, hooks).

Gradient fill shows no colour.

Cause. The <linearGradient> id isn't unique / not referenced.

Fix. Give ChartGradient a useId() id and set fill={url(#${id})}.

useChart must be used within a <ChartContainer />.

Cause. A tooltip/legend content or hook rendered outside the container.

Fix. Keep them inside ChartContainer (which provides the config context).

Charts flake in screenshot/visual-regression runs (marks mid-entrance).

Cause. The ~1.5 s entrance animation is JS-driven and still running at capture time.

Fix. Set animate={false} on ChartContainer, or emulate prefers-reduced-motion: reduce in the test browser — both render the final frame immediately.

A custom series (or function-as-child chart) still animates under reduced motion.

Cause. The container's isAnimationActive defaulting only reaches the library's own elements at the chart's top level.

Fix. Read useChart().animate and pass it to your element's isAnimationActive yourself.

Accessibility

A1.1.1Non-text ContentA1.4.1Use of ColorAA1.4.11Non-text ContrastA2.1.1KeyboardAAA2.3.3Animation from Interactions
  • Give the chart an accessible name (a `<figcaption>` + `aria-labelledby`, or `aria-label`).
  • Provide a text/data-table equivalent — `ChartDataTable` generates one from the same config + data.
  • Enable the library's `accessibilityLayer` for a single tab stop + arrow-key point navigation + tooltip-on-focus.
  • Distinguish series without colour alone (markers / dashes / direct labels); series colours meet ≥3:1 in both themes.
  • Series entrance animation is disabled automatically under `prefers-reduced-motion` (the container defaults `isAnimationActive` onto its series) — don't re-enable it per-series without cause.