Skip to main content

ComposedChart

Change over Timechart

One cartesian frame that layers several series types (bars + lines + areas) together.

import { ComposedChart } 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 ComposedChart 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 ComposedChart up before writing code: `npx -y @elabs-ai/components-cli docs ComposedChart` 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.

Use it for

  • How did it change over time? Two linked measures.
  • mixed marks on one shared axis — bars with a line target, for example

Avoid

  • a single mark type would do — reach for that container directly
  • Stacking series on two silently-different y-scales — label the second axis or the comparison lies.
  • Layering more than ~3 series types in one frame — split it into small multiples instead.
  • Colouring series by hand — resolve --chart-1..5 so the dark theme stays legible.

Works with

Pairs with

Examples

9 live examples

Default

Bar + area + line on a shared time scale — the canonical ComposedChart composition.

With Accessible Label

Accessible variant — announces label + description to screen readers on focus.

Stacked Bars

Two bar series stacked, plus a trend line.

Loading

Loading vs ready (#268) — shares AreaChart/LineChart's skeleton + grid-shimmer chrome.

Selection states

`selectionStates` paints the host’s tri-state on each category’s points: selected marks carry a compound foreground/background outline, excluded marks dim AND carry a full-opacity dashed frame, so the three states stay distinguishable in greyscale.

Legend toggle

Dual Axis

Columns on the left scale, a line on the right (Datawrapper’s dual-axis rules: different units, different mark types, both zero-based, colour-matched axis labels). `align: "ticks"` puts both scales on the same gridlines.

Dual Axis Proportional

Two lines, neither zero-based: `proportional` gives both scales one shared origin, so each gridline is the same relative change on both sides.

Stacked Percent

`stacked="percent"`: each month’s channels fill 100 %, so the columns compare shares, not volumes. The value axis prints percent; the tooltip keeps the raw order counts.

API

ComposedChart

PropTypeDescription
datarequiredRecord<string, unknown>[]Data array — each row typically has a date and multiple numeric series
xDataKeystringKey for the x-axis (time). Default: "date"
xScaleChartXScaleTypeHow `xDataKey` values are interpreted (#352). Default: `"time"`. `"band"` = categorical x, `"linear"` = numeric x. See `LineChart.xScale`.
marginnumber | Partial<Margin>Chart margins: one number for every side, or per side. Default: 40 on every side.
animationDurationnumber
animationEasingstring
enterTransitionTransition
revealSignaturestringSignature of motion URL state — triggers reveal replay when it changes.
yDomainTweenDurationnumberAnimate the y-domain over this duration (ms), including a legend toggle's re-tween (RM-118, validator FAIL 1a). Default: 500, same as `LineChart`/`AreaChart`.
aspectRatiostring
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Wins over `aspectRatio`, which stays an alias.
classNamestring
statusChartStatusLoading vs ready — drives chart phase and loading chrome. Default: `"ready"`.
loadingLabelstringCentered shimmer label while loading.
childrenrequiredReactNode
barSizenumberTarget bar width in px (the React chart library-style `barSize`).
maxBarSizenumberMaximum bar width in px (`maxBarSize`).
groupGapnumberGap between grouped `SeriesBar` series in px. Default: 4
barGapnumber@deprecated Since 6.0.0, use `groupGap`. Removed in 7.0.0.
stackedboolean | "percent"Stack `SeriesBar` segments in child order at each x (line/area are not stacked). `"percent"` (RM-121) stacks each x to 100 % through `bar-stacking.ts`: the primary `YAxis` is pinned to 0–100 % and prints percent unless it sets its own format, and the tooltip keeps the raw values. Put line/area series on another `yAxisId` beside it.
stackGapnumberGap in px between stacked segments. It is cut out of the boundaries between segments only, half from each side, so every stack still starts on the baseline and ends at its total. Default: 0
insetBarsbooleanKeep the first and last column inside the plot: the x range is inset by half a slot, so no column runs over an axis. Default `true` — a point scale centres the end columns ON the plot edges and hangs half of each one outside it (a-3). Pass `false` for that older, overhanging geometry.
onPhaseChange(phase: ChartPhase) => void
onDatapointClickChartDatapointClickHandlerDrill-down (#349). Fires when a datapoint is activated by pointer OR keyboard. Setting it mounts a keyboard-operable target layer OUTSIDE the aria-hidden SVG — one tab stop, arrow keys to traverse. Leaving it unset changes nothing: no extra DOM, no new focusables.
copyValueOnActivatebooleanPut the datapoint's exact value on the clipboard when it is activated — the recovery path for a compact axis label. Default `false`; a chart with no interaction props still renders byte-identical DOM. A consumer-supplied `onDatapointClick` always wins.
datapointLabelChartDatapointLabelOverride the accessible name of each keyboard drill-down target (#349).
maxInteractiveDatapointsnumberDev-warning threshold on the number of keyboard targets. Default 500 (#349).
accessibleLabelChartA11yProps["accessibleLabel"]Accessible name for the chart region (announces to AT on focus).
accessibleDescriptionChartA11yProps["accessibleDescription"]Supplemental description read by AT (e.g. series names + value range).
legendContainerLegendPropLegend engine (RM-118): `true` or a config object mounts `ChartLegend` beside the plot via `useContainerLegend`; `{ interactive: "toggle" }` hides a series and re-tweens the y-domain. Unset (default) renders NOTHING new — same R1 as `LineChart`/`AreaChart`. `{ values: true }` prints each series' last point inside the visible x window, in the format of the `YAxis` on that series' `yAxisId`. The legend formats every entry alike, so on dual axes whose `valueFormat` or `currency` differ (currency left, percent right), and on a percent stack, it prints plain numbers rather than one axis' unit on the other's series.
yAxesDualAxisOptionsTwo value axes (RM-121). Set it, give each series a `yAxisId` (`SeriesBar` draws on the primary `"left"` axis) and place both `YAxis` as direct children: `align: "ticks"` (default) gives both axes one tick count on the same pixel rows, `proportional` makes them grow by the same factor, and `zero` applies the "both or neither" baseline rule. Columns and areas stay zero-based whatever is asked (`charts-honesty`). Both axes stay visible at the narrow tier. Unset: every axis is independent, as before.
annotationsreadonly ChartAnnotation[]Declarative annotations in data units: text notes, ranges, reference lines, row notes.
analyticsreadonly ChartAnalytic[]Statistical overlays computed from `data` (ADR 0040 §1): computed `line`/`band`s (average, median, percentile, std-dev, CI) drawn through the annotation layer, and `trend`/`window`/`forecast`/`errorBars` drawn as derived series with a legend entry, a tooltip row and an accessible sentence. Unset: no change.
tooltipbooleanShow a hover/focus tooltip. Default `true`: with no `<ChartTooltip>` child the chart adds a default one; a `<ChartTooltip>` child (for `variant`, `rows`, `content`, …) replaces it. `false` turns the default off.
paletteChartPaletteColour ramp for the series (RM-186): each series child that sets no colour of its own takes the next colour of `resolvePalette(palette, n)`. Unset: every such series keeps `--chart-line-primary`, as before.
selectionStatesChartSelectionStatesResolver<Record<string, unknown>>Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before.
dimExcludedbooleanDim `excluded` marks and add their non-hue channel (the dashed frame). Default `true`; `false` keeps the `data-selection` attribute (so a host can style it) but paints nothing.
hoverCategoryChartHoverCategoryThe category hovered in a SIBLING chart (or by the host). `null`/unset → none.
onHoverCategory(category: ChartHoverCategory) => voidFires with the category under the pointer on move, and `null` on leave.
scrollbarChartScrollbarModeStrip style. Default `"none"` — a chart never grows a strip on its own (ADR 0040 decision b); a `window` / `defaultWindow` turns it on, as does `"miniChart"` / `"bar"` / `"auto"`.
windowNavigatorWindow | nullControlled window.
defaultWindowNavigatorWindowInitial window for the uncontrolled case.
onWindowChange(window: NavigatorWindow | null, meta: NavigatorChangeMeta) => void
minSpannumberSmallest window the user can make: a duration in ms (time) or a row count (index). Default: 5× the median step (time; the stock-chart library' rule) / 3 (index).
align"start" | "end"Where an automatic first window sits. `"end"` = the latest data (the associative BI suite `scrollStartPos: 1`). Default `"start"`.
maxVisibleItemsResponsive<number | "auto">Category families (RM-141): how many categories the plot shows at once (the associative BI suite "Number of bars"). Default `"auto"` — as many as keep a readable band (`maxReadableCategories`); with `scrollbar="auto"` the strip appears only once the categories overflow that count.
maxVisiblePointsnumberTime-series families: with `scrollbar="auto"`, rows above this show the strip. Default 2000.
windowDomain"all" | "visible"Category families: `"all"` (default) keeps the value axis on the FULL data's domain while scrolling; `"visible"` refits it to the window.
zoombooleanPinch-to-zoom along the x axis (time x): two fingers on a touch screen, a trackpad pinch, or Ctrl/⌘ + wheel narrow the same window the strip moves; a two-finger drag pans it. `+` / `−` / `0` on the focused chart and the zoom buttons that appear once zoomed do the same from the keyboard. No strip is needed; with one, it follows. Default `true`; `false` leaves the gestures to the page. Off while the caller drives `xDomain` itself.
selectionGesturesreadonly ChartSelectionGesture[]Gestures to enable. Unset → no gesture layer, DOM byte-identical to today.
onSelectionIntentChartSelectionIntentHandler<Record<string, unknown>>Fires with every intent. Required for the gesture layer to mount.
selectionConfirmChartSelectionConfirmDefault `"immediate"`.
selectionFieldstringThe field name carried in intents. Default: the chart's `xDataKey`.
selectionHitRuleChartSelectionHitRuleRect / lasso hit rule. Default `"overlap"` (the report-builder BI suite).
selectionToolbar"auto" | "none"`"auto"` (default): a toolbar appears when gestures are enabled; `"none"` hides it.

Also accepts: ChartSelectionProps, ChartHoverLinkProps, ChartNavigatorProps, ChartSelectionGestureProps, FrameSizeGroupProps, Pick<LegendGroupProps, "legend">, Pick<ChartStateGroupProps, "status">, Pick<TooltipGroupProps, "tooltip">

XAxis

PropTypeDescription
numTicksnumber@deprecated Use `tickCount` — `numTicks` still wins when both are set, exactly as before (ADR 0042 A.2, row 6). Read until 7.0.0, with one development warning.
tickCountAxisTickCountTick target (RM-108). `"auto"` (default) derives it from the plot width — `tickTargetForWidth(innerWidth)`, about one tick per 90 px, clamped 2–10, and never more than the number of data rows on a time axis (6 monthly rows get at most 6 ticks, not 9 labels between them).
ticksDate[] | number[]RM-108 naming, shared with `YAxis`. On a time axis: an alias of `tickValues` (`tickValues` wins). On a numeric x with `domain`/`scale` (ScatterChart): exactly these raw x values.
domainAxisDomainNumeric x only (ScatterChart with numeric `xDataKey`, RM-108): pin either end of the x domain in raw units; `"auto"` keeps the data-derived end. Read by the chart container, so place `XAxis` as a direct child. Setting `domain` or `scale` also switches the labels from one per data row to a numeric ruler. Ignored on a time axis.
scaleValueScaleTypeNumeric x only (RM-108): `"linear"` (default), `"log"` (refuses data or a domain touching 0 — dev warning, renders linear) or `"sqrt"`.
positionXAxisOrientationWhich edge the labels sit on (RM-108). Default: `"bottom"`.
orientationXAxisOrientation@deprecated Use `position` — the same values (ADR 0042 A.2, row 10). Read until 7.0.0, with one development warning; when both are set, `position` wins.
titleReactNodeAxis title (RM-108).
titlePlacementAxisTitlePlacement`"outside"` (default) — in the margin beyond the tick labels, at the far end; `"inside"` — `HaloText` at the far end inside the plot.
tickerHalfWidthnumberWidth of the date ticker box for fade calculation. Default: 50
tickMode"domain" | "data"`"domain"` — evenly spaced ticks across the time domain (default). `"data"` — one label per data row at its x value (better with sparse or monthly bars).
tickFormat(value: Date) => stringOverride the default `Intl`-based tick label formatter. Applied to every tick this axis renders (generated or `tickValues`). #357. **Time scale only.** Ignored (with a dev warning) when the chart runs `xScale="band" | "linear"` — those modes project onto a synthetic instant, so a `Date` formatter would print a fabricated calendar date instead of your x value. Format categorical/numeric x values in your data. #352.
dateFormatDateFormatPreset | ((value: Date) => string)The date-format LADDER (RM-109): a preset from `date-format.ts` (`"year"` → `"yearShort"` → `"month"` → `"day"` → `"weekday"` → `"hour"` → `"minute"`), or a formatter function for full control. Unset (default) — the axis picks a rung itself via `dateFormatForSpan`, from the time domain's span and how many ticks are on screen, instead of the old fixed `"Mon d"` shape whatever the span. A ten-year series reads years; a 36-hour series reads hours. `tickFormat` still wins outright when both are set — it is the raw escape hatch this package has always had; `dateFormat` is the smart default underneath it. Same time-scale-only caveat as `tickFormat` (#352).
tickValuesDate[]Render exactly these tick positions, bypassing tick generation AND the default label-collision de-dupe entirely — use when the default de-dupe would collapse the axis (e.g. dense data whose formatted labels collide). #357. **Time scale only** — see `tickFormat`. #352.
periodTicksXAxisPeriodTicks | falseDraw a `HairlineFloor` (RM-017) below the plot — one hairline tick per calendar period, whether or not a data row falls on it, with the long tick anchored to a real calendar boundary (day → the first day of the week, week → the first week of the month, month → January), not an index stride from wherever the series happens to start (#253). Lieflat provenance: the ruled foot of a ledger page gives a reader the passage of time with no labels and no axis. This is a SEPARATE visual layer from the labelled ticks above (`numTicks` / `tickMode`) — it never changes which labels render or where. `false` (default) draws nothing — today's behaviour.

YAxis

PropTypeDescription
yAxisIdstring | numberScale group id (Recharts `yAxisId`). Default: `"left"`.
positionYAxisOrientationWhich side of the chart to render labels. Default: `"left"`.
orientationYAxisOrientation@deprecated Use `position` — the same values (ADR 0042 A.2, row 11). Read until 7.0.0, with one development warning; when both are set, `position` wins.
numTicksnumber@deprecated Use `tickCount` — `numTicks` still wins when both are set, exactly as before (ADR 0042 A.2, row 7). Clamped to {@link Y_AXIS_MIN_TICK_COUNT}–{@link Y_AXIS_MAX_TICK_COUNT}. Read until 7.0.0, with one development warning.
tickCountAxisTickCountTick target (RM-108). `"auto"` (default) derives it from the plot height — `tickTargetForHeight(innerHeight)`: 3 under 200 px, else 5.
ticksnumber[]Render exactly these tick values (RM-108), bypassing generation.
domainAxisDomainPin either end of the value domain (RM-108); `"auto"` keeps the data-derived end. Read by the chart container, so place `YAxis` as a direct child. On a length encoding (bars) the domain always includes 0 — a non-zero lower bound is ignored with a dev warning (`charts-honesty`).
scaleValueScaleTypeHow values map to pixels (RM-108). `"log"` refuses data or a domain that touches 0 (dev warning, renders linear); bars are linear only. Default: `"linear"`.
titleReactNodeAxis title — names the unit of every tick (RM-108).
titlePlacementAxisTitlePlacement`"outside"` (default) — above the top tick label in the margin; `"inside"` — `HaloText` at the top of the axis inside the plot.
labelPlacementAxisLabelPlacement`"outside"` (default) — tick labels in the margin beside the plot; `"inside"` — labels sit just above their grid line inside the plot.
valueFormatChartValueFormatHow tick values are rendered. Default: `"compact"` — `1.5M`, not `1500k` and not `1500000`. Pass `"number"` for every digit.
currencystringISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`.
formatValue(value: number) => stringCustom formatter for tick labels. Overrides `valueFormat` entirely when set.
unitstringUnit text appended to {@link unitOn} tick(s) — `<YAxis unit="%" />` paints `"80 %"` on that tick, not every tick (RM-109). The tooltip is the place a unit repeats on every value (the blog's "3.4 % unemployed", not a bare "3.4 %"); see `TooltipRow.unit`.
unitOn"last" | "first" | "all"Which tick(s) {@link unit} paints on. Default: `"last"`.
matchSeriesColorbooleanPaint the tick labels and title in the colour of this axis' series when it carries exactly ONE series; neutral otherwise (RM-121, Datawrapper's "label colour: match data"). The text goes through `seriesLabelInk` — the stroke mixed toward the label ink so it still reads at 4.5:1. Default `false`.
sideLabelReactNode | "auto"A caption naming which scale this is, above the tick column (RM-121). `"auto"` → "Left scale" / "Right scale" by `position`, through the locale seam (`charts.axis.leftScale` / `charts.axis.rightScale`).

Grid

PropTypeDescription
modeGridModeGrid painting mode (RM-108). Default: `"lines"`.
horizontalbooleanShow horizontal grid lines. Default: true
verticalbooleanShow vertical grid lines. Default: false
numTicksRowsnumberNumber of horizontal grid lines. Default: the height-derived target shared with `YAxis` (`tickTargetForHeight(innerHeight)` — 3 under 200 px, else 5), so rows and labels stay aligned (RM-108).
numTicksColumnsnumberNumber of vertical grid lines. Default: 10
rowTickValuesnumber[]Explicit tick values for horizontal grid lines. Overrides numTicksRows.
strokestringGrid line stroke color. Default: var(--chart-grid)
loadingStrokestringGrid stroke while loading chrome is active. Falls back to `stroke`.
strokeOpacitynumberGrid line stroke opacity. Default: 1
strokeWidthnumberGrid line stroke width. Default: `CHART_HAIRLINE_WIDTH` — the one furniture weight.
strokeDasharraystringGrid line dash array. Default: "4,4" for dashed lines
highlightRowValuesnumber[]Horizontal row values rendered with alternate styling (e.g. zero baseline).
highlightRowStrokestringStroke for highlighted rows. Default: var(--chart-foreground-muted)
highlightRowStrokeOpacitynumberStroke opacity for highlighted rows. Default: 1
highlightRowStrokeWidthnumberStroke width for highlighted rows. Default: 1
highlightRowStrokeDasharraystringDash array for highlighted rows. Default: solid line
highlightRowLabel(value: number) => string | undefinedText label drawn at the right edge of each `highlightRowValues` entry (`HaloText`, so it survives sitting on the grid) — a KPI target/goal line the reader can name, not just see. Return `undefined`/`""` to skip the label for a given value. Unset (default): no label, byte-identical to before this prop existed.
highlightColumnValuesArray<number | Date>Vertical reference lines at these `xDataKey`-domain values (e.g. "today" on a forecast chart) — the column counterpart of `highlightRowValues`. Default: none.
highlightColumnStrokestringStroke for highlighted columns. Default: var(--chart-foreground-muted)
highlightColumnStrokeOpacitynumberStroke opacity for highlighted columns. Default: 1
highlightColumnStrokeWidthnumberStroke width for highlighted columns. Default: 1
highlightColumnStrokeDasharraystringDash array for highlighted columns. Default: solid line
highlightColumnLabel(value: number | Date) => string | undefinedText label drawn at the bottom-inside, left of each `highlightColumnValues` entry (`HaloText`), e.g. "Today". Return `undefined`/`""` to skip the label for a given value. Unset (default): no label.
fadeHorizontalbooleanEnable horizontal fade effect on grid rows (fades at left/right). Default: true
fadeVerticalbooleanEnable vertical fade effect on grid columns (fades at top/bottom). Default: false
yAxisIdstring | numberY-scale for horizontal grid lines. Default: primary (`"left"`) axis.
shimmerbooleanAnimate a shimmer band across horizontal grid lines. Default: false
shimmerStrokestringShimmer band stroke (color and opacity via color-mix or oklch alpha).
shimmerLengthnumberShimmer band width in pixels. Default: 140
shimmerSpeednumberShimmer speed multiplier (higher = faster). Default: 1
shimmerSyncbooleanMatch loop timing to the loading line pulse (cycle + inter-loop pause).

ChartLegend

PropTypeDescription
itemsrequiredLegendItem[]Legend items to display
hoveredIndexnumber | nullCurrently hovered index (for highlight effect)
onHover(index: number | null) => voidCallback when an item is hovered
onItemClick( item: LegendItem, index: number, event: React.MouseEvent | React.KeyboardEvent, ) => voidDrill-down on a legend entry (#349). When set, each item renders as a real `<button>` — keyboard operable and named by the item's label — instead of a `<div>`. Unset, the legend is unchanged (a non-interactive `<div>` list). NOTE this lives on `ChartLegend`, not on the chart containers: the legend is a separately-placed composition primitive, so a callback on the container would be a prop the container cannot honour.
hiddenKeysReadonlySet<string>Series toggle (RM-118, `legend={{ interactive: "toggle" }}`). Set together with `onToggleKey`: every item renders as a real `<button aria-pressed>` — native keyboard support, no extra wiring — and `hiddenKeys` marks which ones read `aria-pressed="false"` and paint dimmed with a struck-through label (WCAG 1.4.1: hidden is never colour-only). Independent of `onItemClick` (drill-down); a container sets one or the other.
onToggleKey(key: string, item: LegendItem, index: number) => voidToggles one item's hidden state. Keyed by `item.key ?? item.label`.
toggleControl"item" | "checkbox"What hides an item when `onToggleKey` is set. `"item"` (default): the whole entry is the `aria-pressed` toggle. `"checkbox"`: the entry stays free for `onItemClick` (a host's selection, say) and a small checkbox after it toggles visibility. The checkbox shows while the row is hovered or holds focus, and always on touch screens, which cannot hover. A hidden entry stays in the legend, muted and struck through, in both modes.
hideAtDensityreadonly ChartDensity[]Density tiers that hide the whole legend (RM-072 default: `xs`/`sm`). The container legend engine (`useContainerLegend`, RM-118) passes `["xs"]` — `sm` renders `stack`-laid-out instead of hiding — so this stays `["xs", "sm"]` for every OTHER (direct) caller, unchanged.
showProgressbooleanShow progress bars. Default: false
showMarkerbooleanShow color marker dot. Default: true
showValuebooleanShow numeric value column. Default: true
showPercentagebooleanShow percentage value. Default: true when showProgress is true
formatValue(value: number) => stringFormat function for displaying values. Overrides `valueFormat` entirely when set.
valueFormatChartValueFormatHow to format `value`/`maxValue` (RM-109) — a preset or the object form (`{ decimals, abbreviate, sign, prefix, suffix, … }`, `value-format.ts`). Resolved as a SET across every item (#250, `useChartValueSetFormatter`) so the legend never mixes "1K" beside "400". Default: locale-aware plain grouping (`intFmt`), unchanged from before this prop existed.
currencystringISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`.
maxFractionDigitsnumberMost digits after the decimal point in the value column (RM-187).
localestringLocale for the value and percent columns (RM-187); unset, the `LocaleProvider`'s.
titlestringTitle shown above the legend
layout"row" | "stack" | "split"Item flow direction (RM-118, sitting 3). `"stack"` (default — every caller before this prop existed) is byte-identical to before: one item per line. `"row"` wraps items left-to-right (`flex-wrap`), matching the layout `AutoChart`'s old `AutoLegend` used, AND drops the `w-full` an interactive (`onItemClick`/toggle) item otherwise gets — `w-full` inside a row would force every item onto its own line regardless of this prop. `useContainerLegend` (the container legend engine) passes its own resolved `layout` ("row" at `base`/`medium`/`wide`, "stack" at `narrow` or density `sm`) through; a direct caller may set its own or leave it unset for the original stacked behaviour.
classNamestringAdditional class name for the container
titleClassNamestringClass name for the title
itemClassNamestringClass name for each legend item
labelClassNamestringClass name for the label
valueClassNamestringClass name for the value
renderItem(props: { item: LegendItem; index: number; isHovered: boolean; isFaded: boolean; percentage: number; }) => ReactNodeCustom render function for legend items. `item.value` may be `NaN`, meaning the item has no value (a container legend entry with no number of its own): check `Number.isFinite(item.value)` before printing it. `percentage` is 0 for such an item.
splitGroupsreadonly ChartLegendSplitGroup[]`layout="split"` (RM-121): one row per value axis, each led by its side label. Items not named by any group are omitted. Unset, `"split"` renders as `"row"`.
splitStackedbooleanStack the split rows vertically (the narrow tier). Default `false`: side by side, wrapping.

ChartTooltip

PropTypeDescription
showDatePillbooleanWhether to show the date pill under the plot. Default: shown when it fits the chart's bottom margin (≥ 36px) — never painted over a small plot.
showCrosshairbooleanWhether to show the vertical crosshair line. Default: true
showDotsbooleanWhether to show dots on the lines. Default: true
indicatorColorstring | ((point: Record<string, unknown>) => string)Color for the crosshair/indicator line. When a function, receives the hovered point (e.g. for candlestick: match candle color from close vs open). Default: --chart-crosshair.
content(props: { point: Record<string, unknown>; index: number }) => React.ReactNodeCustom content renderer for the tooltip box
rows(point: Record<string, unknown>) => TooltipRow[]Custom row renderer - return array of TooltipRow
variantChartTooltipVariantBox layout preset (RM-119). `"rows"` (default) is today's stacked `ChartTooltipContent`. `"table"` renders `ChartTooltipTable` — one column per series with a header row, the date in the `<caption>` (Datawrapper's dual-axis / multi-series shape, `dw-charts.md` §2.22). `"inline"` renders no box at all — `ChartTooltipInline` paints the hovered (or `focus`-nearest) series' value directly at the mark with `HaloText` (the bikes chart). Ignored when `content` is set — a custom renderer already owns the whole box.
focusbooleanDrive RM-112's per-series dim (`SeriesHoverDim`) from the tooltip's OWN nearest-series resolution: whichever series' y position is closest to the pointer, at ANY x along the crosshair, counts as hovered — not only a direct hover on that series' own 2px stroke. Works standalone: it registers "focus requested" on the shared series-mode context, so the dim fires with no `focusOnHover` prop on the `LineChart`/`AreaChart` container (a container `focusOnHover` still works exactly as before, and the two compose — either one turns the dim on). Also the default target series for `variant="inline"`. Default false.
pinbooleanTap-to-pin (RM-119): on a coarse pointer, a tap keeps the tooltip open instead of clearing the instant the finger lifts. A second tap on the mark, `Esc`, or a tap outside the chart releases it (announced once via `role="status" aria-live="polite"`). Default: `true` only when the pointer is coarse (`(pointer: coarse)` media query); pass an explicit boolean to force either way regardless of pointer type.
valueInTitlebooleanSwap the tooltip box's own title for nothing — the hovered value moves to the chart's OWN title instead (RM-119, #610). Inside a `ChartFrame` that shows a title this is automatic: while a row is hovered, pinned or keyboard-focused the frame's title reads "Apr: Revenue 40" and is announced once through the frame's polite status; leaving restores the title. No wiring. Outside a frame (or in `chrome="bare"`, a custom `headerSlot`, the expanded dialog) only the box title is dropped — the date/category is still on `useChart().tooltipData` for a custom title. Default false.
dotColorstring | ((point: Record<string, unknown>, line: LineConfig) => string)Override tooltip dot fill. When omitted and `rows` is set, dot colors match row colors. When a function, receives the hovered point and line config.
childrenReact.ReactNodeAdditional content to show below rows (e.g., markers)
classNamestringCustom class name
springConfigSpringConfigPer-chart override for the crosshair / dot / date-pill spring.
boxSpringConfigSpringConfigPer-chart override for the floating-panel spring.
panelStyleReact.CSSPropertiesInline styles for the tooltip panel (background, blur, etc.).
unitstringUnit text appended to every DEFAULT row's value (RM-109) — the same `unit` `<YAxis>` paints on one tick. Set automatically from the chart's own `<YAxis unit>` when unset (the shell threads it through); pass it explicitly only to override. Ignored when `rows` is set — a custom row builder owns its own `TooltipRow.unit` per row.
valueFormatChartValueFormatSTYLE to borrow from the chart's own `<YAxis valueFormat>` for DEFAULT rows (RM-109) — a currency symbol, a percent sign — never abbreviation (`ChartTooltipContent`'s doc). Set automatically from `<YAxis valueFormat>` when unset; ignored when `rows` is set.
currencystringISO 4217 code for `valueFormat`'s `style: "currency"`.