Skip to main content

AutoChart

Containers and Specschart

Spec-driven chart — hand it a serializable ChartSpec and it picks and renders the right chart.

import { AutoChart } from "@elabs-ai/components-charts";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui AutoChart chart (from @elabs-ai/components-charts) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look AutoChart up before writing code: `npx -y @elabs-ai/components-cli docs AutoChart` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-charts is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Avoid

  • Hand-picking a chart type when the spec already encodes the intent — AutoChart is the agent-facing entry point.
  • Putting raw colors in a spec's series — the chart-1…chart-5 tokens are the palette in every theme.
  • Passing an empty dataset with no not-ready signal — render the loading state instead of an empty frame.

Works with

Pairs with

Examples

38 live examples

With Analytics

`spec.analytics` — the serialisable overlays an agent can ask for: here an average line, the 25–75 percentile band and a linear trend with its r² in the legend. No callbacks in a spec; `AutoChart` hands the list to the container unchanged.

Bar Grouped

Grouped bar chart with three regional series.

Bar Stacked

Stacked bar chart — same data, stacked mode.

Donut

Donut chart with a center hole.

Scatter

Scatter chart with numeric x (ad spend vs conversions).

Radar

Radar chart comparing two teams across five metrics.

Funnel

Funnel chart showing a conversion pipeline.

Currency Formatted

Currency-formatted y-axis via the `valueFormat` hint (line chart).

Copy Exact Value

Values are compacted on the axis and in the tooltip, so the exact figure has to stay reachable: `AutoChart` defaults `copyValueOnActivate` to `true`, which mounts the keyboard datapoint layer (real buttons OUTSIDE the aria-hidden `<svg>`) and a persistent polite live region for the "copied" announcement. Clicking or pressing Enter on a point copies its exact value.

Copy Exact Value Disabled

`copyValueOnActivate={false}` opts the tile out entirely — no datapoint layer, no live region, and the chart's DOM is what it was before the feature existed.

Unsupported Fallback

A type outside the `ChartType` union renders a `ChartFallback`. `sankey` is one of the four shapes AutoChart deliberately never infers or renders — a flat spec cannot express a node/link graph unambiguously, so it stays explicit-container-only. The panel speaks to the reader (“This chart can’t be displayed.”) and is a settled result, not a live region; the type name goes to a dev console warning instead (#304).

Empty Data

Empty data array renders a "No data to display" fallback.

Loading

Loading vs ready (#268) — a layout-shaped skeleton at the chart's normal height.

In Chat Conversation

InChatConversation — shows AutoChart embedded inside an AI tool-call output. Uses a minimal token-styled mock shell (plain divs) instead of the full `@elabs-ai/components-ai` Conversation/Message/Tool stack, keeping this story self-contained within the `@elabs-ai/components-charts` package (which does NOT depend on `@elabs-ai/components-ai`). The REAL composition with `@elabs-ai/components-ai`'s Conversation/Message/Tool/ToolOutput components lives in the copy-owned `ai-chart` registry block (`registry/blocks/ai-chart/`) — a registry block may import both siblings, whereas a package (and therefore a package's story) may not. See `research/ai-charts/01-ai-chart-integration-plan.md`.

Candlestick Inferred

OHLC columns on a date axis are candles, never a line.

Treemap Inferred

A nested hierarchy is the one shape only a treemap can read.

Treemap Palette

`palette` on a treemap spec (#306): colour is a DATA encoding here — `"categorical"` encodes the top-level group, `"sequential"` the leaf value, `"mono"` (the default) nothing. Everything else about the spec is unchanged.

Bump Inferred

Long `(period, entity, rank)` rows DECLARED as a ranking. Without the declaration the identical shape reads as a heatmap — the two cannot be told apart structurally, which is why the ranking rule runs first and asks for a signal.

Calendar Inferred

A year of dated rows is more days than a line can resolve.

Stream Inferred

Stacked bands over time read as a streamgraph, not as lines.

Heatmap Inferred

Two categorical keys and one measure make a matrix, whatever the row count.

Dumbbell Inferred

Two measures whose names read as before/after are one measure twice.

Histogram Inferred

A bare column of observations has no category to plot against.

Strip Inferred

Few enough records per group and every one can still be drawn.

Box Inferred

Past ~200 records a group, the summary reads better than the records.

Waterfall Inferred

A total/net/gross checkpoint row makes these deltas rather than categories — and it outranks the diverging rule, which this same data also satisfies.

Diverging Bar Inferred

A single measure that crosses zero: the baseline is the story.

Unit Inferred

The same shares that would draw a pie draw countable marks instead once the spec asks for the editorial register — `emphasis: "editorial"` is the only difference between this story and a pie.

Unit plotHeight forwarding (400px)

`AutoChart`'s own `plotHeight` prop, forwarded straight to `UnitChart`'s `plotHeight` (not the deprecated `height` alias) — a regression lock for G1 (RM-183 review round 2): a `ChartPlotBox` bug erased every resolved plot height on `layout="waffle"`/`"field"`, collapsing this to the 100px content floor regardless of the 400px asked for here.

Line Legend From Column Keys

A series known only by its column key (no `label`) is NOT end-labelled (RM-110), so the legend engine's default shows a legend — forwarded through `spec.legend` into `LineChart`'s own `legend` prop (`LEGEND_ENGINE_TYPES`), never a second, `AutoChart`-owned implementation. The rendered `.legend-container` is byte-identical to an explicit `<LineChart legend>` built from the same normalized series (see `auto-chart.test.tsx`'s "renders the identical legend DOM…" test, and `LineChart`'s own `LegendToggle` story for the equivalent explicit call).

Line Faceted Legend

`ChartSpec.facet` (RM-120) splits one line spec into a grid, one panel per `region` — and `legend: true` (RM-118) shows ONE shared `Chart legend` above the whole grid, not one per panel and not the old per-chart `AutoLegend` a faceted spec briefly lost across the wave-2 branch merge.

Bar Faceted Legend

RM-118 Part B × RM-120, sitting 2: `bar` joined `LEGEND_ENGINE_TYPES` this wave, so a faceted bar spec gets the same fix as `LineFacetedLegend` above — ONE shared `Chart legend` over the whole grid, never one per panel.

Pie Faceted Legend

RM-118 Part B × RM-120, sitting 2 (fix round 1, item 3): the pie half of `BarFacetedLegend` above — same ONE-shared-`Chart legend`-over-the-grid treatment, but the items must be the slice CATEGORIES (deduped across panels via `pieRows`, the same source the retired `<AutoLegend>` used), never the value column and never one legend per panel. This is the exact regression class the sitting-2 `legendItems`-not-`series` fix in `auto-chart.tsx` guards — see the matching unit tests in `auto-chart.test.tsx`'s "AutoChart faceted pie legend" block.

Dual Axis Spec

`type: "dual-axis"`: order counts as columns on the left scale, the conversion rate as a line on the right. `axes.y2.align: "ticks"` puts both scales on the same gridlines; the legend splits by side and the tooltip is a table with a header per axis.

Dual Axis Spec Without Line

A dual-axis spec with no line series cannot be drawn honestly: `ChartFallback` says so.

Choropleth Spec

`type: "choropleth"`: the rows join the bundled US-states map on their postal code, and a stepped quantile scale gives every class the same number of states. The map is fetched only when a spec names one, so a bar-chart spec never pays for it. At the narrow tier the colour key moves below the map and the place names step aside.

Choropleth Spec Without Map

A choropleth spec with no map cannot be drawn: `ChartFallback` says so.

API

AutoChart

PropTypeDescription
specrequiredChartSpecThe serializable chart specification produced by an LLM tool-call. `spec.type` is optional; omit it and the data shape decides (see `explainChartType`). The full `ChartType` union AutoChart renders is: `line` | `area` | `bar` | `pie` | `scatter` | `radar` | `funnel` | `candlestick` | `heatmap` | `calendar` | `waterfall` | `dumbbell` | `unit` | `treemap` | `histogram` | `box` | `strip` | `bump` | `stream` | `diverging-bar` | `dual-axis` | `choropleth`. Anything else — including `network`, `parallel`, `tree` and `sankey`, which stay explicit-container-only — renders `ChartFallback` instead. That list is not hand-kept prose: `auto-chart.test.tsx` parses it back out of this comment and asserts it equals `CHART_TYPES`, so a type added to the union without being documented here fails the suite.
plotHeightResponsive<ChartPlotHeight>Height of the drawing area only; the title and legend stack around it. A number of pixels, `{ aspect }` (width / height), or a per-breakpoint `{ base, medium?, narrow? }`. Default: the chart family's own default (`{ aspect: 2 }` wide, `{ aspect: 1.25 }` narrow for the 2:1 families).
heightnumber@deprecated Use `plotHeight`. Removed in 7.0.0. Until then it is read as `plotHeight` and logs one development warning per page.
loadingbooleanLoading vs ready — renders a layout-shaped skeleton at the normal chart height instead of resolving `spec`. Default: `false`.
copyValueOnActivatebooleanCopy a datapoint's EXACT value to the clipboard when it is activated by pointer or keyboard. Default **`true`** here, unlike the raw chart containers, which default it to `false`. The asymmetry is deliberate. `AutoChart` renders compact axis labels (`1.5M`) from a spec its caller never sees the axis config for, so without this there is no way to recover the exact figure. A raw container's caller owns the axis and can format it themselves, and turning this on mounts the datapoint interaction layer — which would break the documented guarantee that a chart with no interaction props renders byte-identical DOM. Set `false` to keep `AutoChart`'s pre-existing DOM (no interaction layer, no keyboard targets).
selectionStatesChartSelectionStatesResolverSelection input (RM-073), forwarded to the chosen container. The resolver's `category` is read from `spec.fields.category` (default `spec.x`), so a host maps selection without knowing the chart type.
dimExcludedbooleanDim `excluded` marks. Default `true`. Forwarded with `selectionStates`.
hoverCategoryChartHoverCategoryShared-crosshair category from a sibling chart (RM-073).
onHoverCategory(category: ChartHoverCategory) => voidFires with the hovered category on pointer move, `null` on leave (RM-073).
onDatapointClickChartDatapointClickHandlerForwarded to the chosen container's own `onDatapointClick` (RM-075), when that family accepts one (bar, line, area/stream, pie, waterfall, heatmap/calendar, dumbbell, unit, treemap, histogram/box/strip, bump, funnel, diverging-bar). Absent on scatter/radar/candlestick — those containers don't accept it yet. Unset by default, so the default render stays byte-identical (#349's own rule: "with it unset the chart renders exactly as before").
onSelectionIntentChartSelectionIntentHandlerSelection intents from the gestures `spec.selection.gestures` enables (RM-145, ADR 0040 §3) — on bar, line, area, scatter, heatmap and the distribution families. With `spec.selection` unset nothing mounts.

Also accepts: Omit<HTMLAttributes<HTMLDivElement>, "title">

Theming

The tokens each state resolves to. Change the token and every theme follows.

StateToken
series--chart-1 … --chart-5