import { ComposedChart } from "@elabs-ai/components-charts";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-chartsPaste 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
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
| Prop | Type | Description |
|---|---|---|
datarequired | Record<string, unknown>[] | Data array — each row typically has a date and multiple numeric series |
xDataKey | string | Key for the x-axis (time). Default: "date" |
xScale | ChartXScaleType | How `xDataKey` values are interpreted (#352). Default: `"time"`. `"band"` = categorical x, `"linear"` = numeric x. See `LineChart.xScale`. |
margin | number | Partial<Margin> | Chart margins: one number for every side, or per side. Default: 40 on every side. |
animationDuration | number | |
animationEasing | string | |
enterTransition | Transition | |
revealSignature | string | Signature of motion URL state — triggers reveal replay when it changes. |
yDomainTweenDuration | number | Animate 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`. |
aspectRatio | string | |
plotHeight | Responsive<ChartPlotHeight> | The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Wins over `aspectRatio`, which stays an alias. |
className | string | |
status | ChartStatus | Loading vs ready — drives chart phase and loading chrome. Default: `"ready"`. |
loadingLabel | string | Centered shimmer label while loading. |
childrenrequired | ReactNode | |
barSize | number | Target bar width in px (the React chart library-style `barSize`). |
maxBarSize | number | Maximum bar width in px (`maxBarSize`). |
groupGap | number | Gap between grouped `SeriesBar` series in px. Default: 4 |
barGap | number | @deprecated Since 6.0.0, use `groupGap`. Removed in 7.0.0. |
stacked | boolean | "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. |
stackGap | number | Gap 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 |
insetBars | boolean | Keep 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 | |
onDatapointClick | ChartDatapointClickHandler | Drill-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. |
copyValueOnActivate | boolean | Put 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. |
datapointLabel | ChartDatapointLabel | Override the accessible name of each keyboard drill-down target (#349). |
maxInteractiveDatapoints | number | Dev-warning threshold on the number of keyboard targets. Default 500 (#349). |
accessibleLabel | ChartA11yProps["accessibleLabel"] | Accessible name for the chart region (announces to AT on focus). |
accessibleDescription | ChartA11yProps["accessibleDescription"] | Supplemental description read by AT (e.g. series names + value range). |
legend | ContainerLegendProp | Legend 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. |
yAxes | DualAxisOptions | Two 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. |
annotations | readonly ChartAnnotation[] | Declarative annotations in data units: text notes, ranges, reference lines, row notes. |
analytics | readonly 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. |
tooltip | boolean | Show 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. |
palette | ChartPalette | Colour 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. |
selectionStates | ChartSelectionStatesResolver<Record<string, unknown>> | Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before. |
dimExcluded | boolean | Dim `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. |
hoverCategory | ChartHoverCategory | The category hovered in a SIBLING chart (or by the host). `null`/unset → none. |
onHoverCategory | (category: ChartHoverCategory) => void | Fires with the category under the pointer on move, and `null` on leave. |
scrollbar | ChartScrollbarMode | Strip 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"`. |
window | NavigatorWindow | null | Controlled window. |
defaultWindow | NavigatorWindow | Initial window for the uncontrolled case. |
onWindowChange | (window: NavigatorWindow | null, meta: NavigatorChangeMeta) => void | |
minSpan | number | Smallest 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"`. |
maxVisibleItems | Responsive<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. |
maxVisiblePoints | number | Time-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. |
zoom | boolean | Pinch-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. |
selectionGestures | readonly ChartSelectionGesture[] | Gestures to enable. Unset → no gesture layer, DOM byte-identical to today. |
onSelectionIntent | ChartSelectionIntentHandler<Record<string, unknown>> | Fires with every intent. Required for the gesture layer to mount. |
selectionConfirm | ChartSelectionConfirm | Default `"immediate"`. |
selectionField | string | The field name carried in intents. Default: the chart's `xDataKey`. |
selectionHitRule | ChartSelectionHitRule | Rect / 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. |
XAxis
| Prop | Type | Description |
|---|---|---|
numTicks | number | @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. |
tickCount | AxisTickCount | Tick 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). |
ticks | Date[] | 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. |
domain | AxisDomain | Numeric 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. |
scale | ValueScaleType | Numeric x only (RM-108): `"linear"` (default), `"log"` (refuses data or a domain touching 0 — dev warning, renders linear) or `"sqrt"`. |
position | XAxisOrientation | Which edge the labels sit on (RM-108). Default: `"bottom"`. |
orientation | XAxisOrientation | @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. |
title | ReactNode | Axis title (RM-108). |
titlePlacement | AxisTitlePlacement | `"outside"` (default) — in the margin beyond the tick labels, at the far end; `"inside"` — `HaloText` at the far end inside the plot. |
tickerHalfWidth | number | Width 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) => string | Override 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. |
dateFormat | DateFormatPreset | ((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). |
tickValues | Date[] | 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. |
periodTicks | XAxisPeriodTicks | false | Draw 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
| Prop | Type | Description |
|---|---|---|
yAxisId | string | number | Scale group id (Recharts `yAxisId`). Default: `"left"`. |
position | YAxisOrientation | Which side of the chart to render labels. Default: `"left"`. |
orientation | YAxisOrientation | @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. |
numTicks | number | @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. |
tickCount | AxisTickCount | Tick target (RM-108). `"auto"` (default) derives it from the plot height — `tickTargetForHeight(innerHeight)`: 3 under 200 px, else 5. |
ticks | number[] | Render exactly these tick values (RM-108), bypassing generation. |
domain | AxisDomain | Pin 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`). |
scale | ValueScaleType | How 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"`. |
title | ReactNode | Axis title — names the unit of every tick (RM-108). |
titlePlacement | AxisTitlePlacement | `"outside"` (default) — above the top tick label in the margin; `"inside"` — `HaloText` at the top of the axis inside the plot. |
labelPlacement | AxisLabelPlacement | `"outside"` (default) — tick labels in the margin beside the plot; `"inside"` — labels sit just above their grid line inside the plot. |
valueFormat | ChartValueFormat | How tick values are rendered. Default: `"compact"` — `1.5M`, not `1500k` and not `1500000`. Pass `"number"` for every digit. |
currency | string | ISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`. |
formatValue | (value: number) => string | Custom formatter for tick labels. Overrides `valueFormat` entirely when set. |
unit | string | Unit 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"`. |
matchSeriesColor | boolean | Paint 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`. |
sideLabel | ReactNode | "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
| Prop | Type | Description |
|---|---|---|
mode | GridMode | Grid painting mode (RM-108). Default: `"lines"`. |
horizontal | boolean | Show horizontal grid lines. Default: true |
vertical | boolean | Show vertical grid lines. Default: false |
numTicksRows | number | Number 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). |
numTicksColumns | number | Number of vertical grid lines. Default: 10 |
rowTickValues | number[] | Explicit tick values for horizontal grid lines. Overrides numTicksRows. |
stroke | string | Grid line stroke color. Default: var(--chart-grid) |
loadingStroke | string | Grid stroke while loading chrome is active. Falls back to `stroke`. |
strokeOpacity | number | Grid line stroke opacity. Default: 1 |
strokeWidth | number | Grid line stroke width. Default: `CHART_HAIRLINE_WIDTH` — the one furniture weight. |
strokeDasharray | string | Grid line dash array. Default: "4,4" for dashed lines |
highlightRowValues | number[] | Horizontal row values rendered with alternate styling (e.g. zero baseline). |
highlightRowStroke | string | Stroke for highlighted rows. Default: var(--chart-foreground-muted) |
highlightRowStrokeOpacity | number | Stroke opacity for highlighted rows. Default: 1 |
highlightRowStrokeWidth | number | Stroke width for highlighted rows. Default: 1 |
highlightRowStrokeDasharray | string | Dash array for highlighted rows. Default: solid line |
highlightRowLabel | (value: number) => string | undefined | Text 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. |
highlightColumnValues | Array<number | Date> | Vertical reference lines at these `xDataKey`-domain values (e.g. "today" on a forecast chart) — the column counterpart of `highlightRowValues`. Default: none. |
highlightColumnStroke | string | Stroke for highlighted columns. Default: var(--chart-foreground-muted) |
highlightColumnStrokeOpacity | number | Stroke opacity for highlighted columns. Default: 1 |
highlightColumnStrokeWidth | number | Stroke width for highlighted columns. Default: 1 |
highlightColumnStrokeDasharray | string | Dash array for highlighted columns. Default: solid line |
highlightColumnLabel | (value: number | Date) => string | undefined | Text 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. |
fadeHorizontal | boolean | Enable horizontal fade effect on grid rows (fades at left/right). Default: true |
fadeVertical | boolean | Enable vertical fade effect on grid columns (fades at top/bottom). Default: false |
yAxisId | string | number | Y-scale for horizontal grid lines. Default: primary (`"left"`) axis. |
shimmer | boolean | Animate a shimmer band across horizontal grid lines. Default: false |
shimmerStroke | string | Shimmer band stroke (color and opacity via color-mix or oklch alpha). |
shimmerLength | number | Shimmer band width in pixels. Default: 140 |
shimmerSpeed | number | Shimmer speed multiplier (higher = faster). Default: 1 |
shimmerSync | boolean | Match loop timing to the loading line pulse (cycle + inter-loop pause). |
ChartLegend
| Prop | Type | Description |
|---|---|---|
itemsrequired | LegendItem[] | Legend items to display |
hoveredIndex | number | null | Currently hovered index (for highlight effect) |
onHover | (index: number | null) => void | Callback when an item is hovered |
onItemClick | ( item: LegendItem, index: number, event: React.MouseEvent | React.KeyboardEvent, ) => void | Drill-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. |
hiddenKeys | ReadonlySet<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) => void | Toggles 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. |
hideAtDensity | readonly 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. |
showProgress | boolean | Show progress bars. Default: false |
showMarker | boolean | Show color marker dot. Default: true |
showValue | boolean | Show numeric value column. Default: true |
showPercentage | boolean | Show percentage value. Default: true when showProgress is true |
formatValue | (value: number) => string | Format function for displaying values. Overrides `valueFormat` entirely when set. |
valueFormat | ChartValueFormat | How 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. |
currency | string | ISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`. |
maxFractionDigits | number | Most digits after the decimal point in the value column (RM-187). |
locale | string | Locale for the value and percent columns (RM-187); unset, the `LocaleProvider`'s. |
title | string | Title 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. |
className | string | Additional class name for the container |
titleClassName | string | Class name for the title |
itemClassName | string | Class name for each legend item |
labelClassName | string | Class name for the label |
valueClassName | string | Class name for the value |
renderItem | (props: { item: LegendItem; index: number; isHovered: boolean; isFaded: boolean; percentage: number; }) => ReactNode | Custom 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. |
splitGroups | readonly 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"`. |
splitStacked | boolean | Stack the split rows vertically (the narrow tier). Default `false`: side by side, wrapping. |
ChartTooltip
| Prop | Type | Description |
|---|---|---|
showDatePill | boolean | Whether 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. |
showCrosshair | boolean | Whether to show the vertical crosshair line. Default: true |
showDots | boolean | Whether to show dots on the lines. Default: true |
indicatorColor | string | ((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.ReactNode | Custom content renderer for the tooltip box |
rows | (point: Record<string, unknown>) => TooltipRow[] | Custom row renderer - return array of TooltipRow |
variant | ChartTooltipVariant | Box 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. |
focus | boolean | Drive 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. |
pin | boolean | Tap-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. |
valueInTitle | boolean | Swap 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. |
dotColor | string | ((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. |
children | React.ReactNode | Additional content to show below rows (e.g., markers) |
className | string | Custom class name |
springConfig | SpringConfig | Per-chart override for the crosshair / dot / date-pill spring. |
boxSpringConfig | SpringConfig | Per-chart override for the floating-panel spring. |
panelStyle | React.CSSProperties | Inline styles for the tooltip panel (background, blur, etc.). |
unit | string | Unit 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. |
valueFormat | ChartValueFormat | STYLE 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. |
currency | string | ISO 4217 code for `valueFormat`'s `style: "currency"`. |