import { LineChart } 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 LineChart 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 LineChart up before writing code: `npx -y @elabs-ai/components-cli docs LineChart` 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? Several series over time.
- one or more measures over continuous time, where the trend itself is the point
Avoid
- many overlapping series (more than about 6) — use small multiples (ChartMultiples), a stream area chart or a composed chart
- A LineChart over unordered categories — a line asserts continuity; use a BarChart.
- More than ~5 series in one plot — past the chart-1…chart-5 palette the series stop being distinguishable; facet instead.
- Distinguishing series by color alone — add markers or direct labels for non-color users.
Works with
Contains
Used inside
Examples
Default
With Reference Line
A labelled threshold on the series’ own y-scale — a target, a budget, an SLA. Drawn in `--chart-foreground` with a dash (meaning, not gridline) and a haloed label; it stays outside the reveal clip like `Grid`.
Multi Series
Loading
Loading placeholders, fixed plot height
Categorical X Scale
Linear X Scale
Non Date X Auto Fallback
Unplottable X Fallback
Custom Tick Format
Duplicate Label Collapse
Drilldown
Click anywhere on the plot — or Tab in and press Enter — to drill in.
Density comparison
#394: the axis tick label reads the `text-meta` ROLE (was the raw `text‑xs` utility, which `data-density`/#340 cannot reach). Two columns pin `data-density` on a plain wrapping div — NOT `<ThemeProvider>`, which writes `data-density` to `document.documentElement` and would race two columns — mirroring `Foundations/Typography → Density scale`'s pattern. The measured font-size is read live from the browser so the columns cannot claim a scaling they do not render. Must match `Gantt`'s already-density-aware timescale tick (11.25px compact / 12px comfortable / 12.75px spacious, styling-and-tokens.md).
Weekend Hollow
`markerStyle` (RM-028): a hollow dot means weekend, a filled dot means weekday — the lieflat "hairline line, weekend/weekday marker" idiom. Every point still gets a marker; only the fill/stroke resolution differs.
Top Three Peaks
`labelPeaks` (RM-028): the top-k highest points get an enlarged marker and a `HaloText` value label — lieflat's "top-2/top-3 peaks, enlarged and labelled" rule. This series has exactly three well-separated spikes, so all three are labelled.
Adjacent peaks forced apart (acceptance)
Barcode Floor
`periodTicks="day"` (RM-028): a `HairlineFloor` tick for every calendar day — 90 of them — whether or not a label renders there, with the long tick anchored to the first day of the week (Monday), not an index stride from the series' start date (#253). The labelled ticks above are a SEPARATE layer — `ninetyDaySeries` starts 2024-01-01, so they land at an even 22-day stride (`Jan 1, Jan 23, Feb 14, Mar 7, Mar 30`), not month boundaries; `periodTicks` never changes that. Acceptance: 90 ticks, every 7th long (a weekly boundary), the long tick painted in its own higher- contrast ink, and no collision between the labelled ticks at 400px width.
With Reference Band
Nulls — gap
`nulls="gap"` (the default) breaks the line at a non-numeric sample — a visible hole, never a silent zero or a straight bridge over missing data.
Nulls — connect
`nulls="connect"` draws one continuous line straight across the missing sample.
Nulls — zero
`nulls="zero"` — the pre-RM-112 default — reads a missing sample as 0.
Curve comparison
`curve` (RM-112) — a named alias over `@visx/curve`. The default changed from `"natural"` to `"monotone"`: `curveNatural` can overshoot past a flat plateau's own value (visible on the middle two, equal-height points below), which reads as a false peak; `curveMonotoneX` is built to never do that. `"step-after"` is shown as a third, structurally distinct alias.
Outline
`outline` (RM-112) paints a `--chart-background` halo under the coloured stroke, so a line keeps reading as one continuous path through a crossing with another series or a busy grid — Datawrapper's line-outline idiom.
Symbols
`symbols` (RM-112) — Datawrapper's line-symbols vocabulary. Unset placement on a ≤12-point series defaults to hollow markers at the first/last point only; a dense series (>12 points) stays unmarked unless `placement` is set explicitly. Same resolution rule, one shared helper, on `Area` too.
Focus on hover
`focusOnHover` (RM-112) dims every OTHER series to `SELECTION_EXCLUDED_OPACITY` while the pointer (or the legend) is over one — a spotlight for a busy multi-series chart. A wide, invisible hit-stroke keeps the hover target reliable even at the default, thin 2.5px stroke. `legend` (issue 545) is the only realistically focusable candidate for a keyboard user to reach the spotlight at all — Tab reaches a legend item, and focusing it drives the identical dim a mouse hover does (`ChartLegend`'s own `onFocus`/ `onBlur`); blurring restores full opacity.
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, right of plot
Legend from column keys
Built by hand from the SAME normalized series `AutoChart`'s `LineLegendFromColumnKeys` story forwards (`spec.series: [{ key: "ebikes" }, { key: "cargo" }]`, no `name` — the legend engine always labels an item by its `dataKey`, never a display `name`). Its `.legend-container` is byte-identical to that story's — proof that `AutoChart` forwards into `LineChart`'s own `legend` prop rather than running a second implementation (Acceptance-4).
Legend at xs and sm density
Density tiers the container legend engine (RM-118) reads from `ChartConfigProvider` (RM-072, a host-picked furniture tier — never derived from viewport width): `xs` has no room at all, so an explicitly-on legend renders nothing; `sm` keeps the legend but always stacks it (one item per line), the same as a `narrow` breakpoint.
Legend suppresses the redundant key row
R4 (sitting 3, RM-118): two long series names overflow RM-110's own end labels at a narrow width, which normally falls back to `SeriesKeyRow` — a compact swatch+name row inside the plot. But `legend` is also forced on here, and the container legend (unlike the pre-RM-118 inline `<ChartLegend>` child) STAYS visible and stacked at narrow by design (Acceptance bullet 1). Rendering both would show the same series list twice, so `legendVisible` (`time-series-chart-shell.tsx`) suppresses the key row whenever the container legend already covers that job — the plot itself never reserves less space, only the redundant row disappears.
Lived-in: growth desk (moving average, forecast, navigator, range)
The way a growth desk actually uses a line: two and a half years of weekly actives, a four-week moving average to read through the noise, an eight-week seasonal forecast at the end, a mini-chart navigator opened on the last year, and a range gesture on the time axis that sizes any period — the caption under the chart reads the intent's `datapoints`. Analytics, navigator and selection are three independent props on the same chart; each one alone leaves the DOM as it was (ADR 0040).
Palette
`palette` (RM-186): series with no `stroke` of their own take the palette's colours in order — here the two ends of the sequential ramp — instead of the single lead-line colour they share when `palette` is unset.
API
LineChart
| Prop | Type | Description |
|---|---|---|
datarequired | Record<string, unknown>[] | Data array - each item should have a date field and numeric values |
xDataKey | string | Key in data for the x-axis (date). Default: "date" |
xScale | ChartXScaleType | How `xDataKey` values are interpreted (#352). Default: `"time"`. - `"time"` — Date (or Date-coercible) x values on a time scale. - `"band"` — categorical x values (`"Turn 1"`, `"Step A"`), evenly spaced in first-seen order. Axis ticks, the ticker and the tooltip title all show your own value, not a formatted date. - `"linear"` — numeric x values spaced by magnitude. Omitting it keeps today's behaviour, except that a dataset whose x values are ALL non-Date-coercible degrades to `"band"` (with a dev warning) instead of collapsing. |
margin | number | Partial<Margin> | Chart margins: one number for every side, or per side. Default: 40 on every side. |
animationDuration | number | Animation duration in milliseconds. Default: 1100 |
animationEasing | string | CSS easing for clip-reveal. Default: cubic-bezier(0.85, 0, 0.15, 1) |
enterTransition | Transition | |
revealSignature | string | |
revealOn | ChartRevealOn | When the enter reveal is allowed to play (#175). `"mount"` (default) plays as soon as the chart renders — no change from today. `"inView"` holds the reveal at width 0 until this chart's own container scrolls to 30% visible. |
replayOnClick | boolean | Clicking the chart body replays the enter reveal (#175). Default `false`. |
aspectRatio | string | Aspect ratio as "width / height". Default: "2 / 1", and "1.25 / 1" at the narrow tier (`DEFAULT_CHART_PLOT_HEIGHT`). `"auto"` leaves the height to an enclosing frame, or else to the caller's CSS. |
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 | Additional class name for the container |
status | ChartStatus | Loading vs ready — drives chart phase and loading chrome. Default: `"ready"`. |
loadingLabel | string | Centered shimmer label while loading. |
yDomainTweenDuration | number | Animate y-domain over this duration (ms) on status transitions. Default: 500. |
yDomainTween | boolean | Animate y-domain when status or target domain changes. Default: true |
xDomain | [Date, Date] | Visible x-domain for brush zoom. |
xDomainSlotCount | number | Full dataset length for x-scale padding when `xDomain` is set. |
tweenYDomainOnXDomainChange | boolean | Tween y-domain when brush changes the visible x-range. Default: false |
style | CSSProperties | Inline container styles (e.g. fixed height for brush strip). |
onPhaseChange | (phase: ChartPhase) => void | Fires when the internal chart phase changes (e.g. OG capture readiness). |
childrenrequired | ReactNode | Child components (Line, Grid, ChartTooltip, etc.) |
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). |
nulls | NullsMode | Container-level default for a `Line`'s own `nulls` prop (RM-112). Unset — every `Line` keeps its own default (`"gap"`). |
focusOnHover | boolean | Hovering (or, on touch, tapping) one series dims every other series to the shared selection-excluded opacity (RM-112, `dw-river.md` §2.3). Default false — today's behaviour. |
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 (R1, moved into `useContainerLegend` itself) — RM-110's end labels stay the default multi-series key for `LineChart`. `{ values: true }` prints each series' last point inside the visible x window (navigator, zoom or `xDomain`), in the format of the `YAxis` on its `yAxisId`; plain numbers when the series' axes format differently. |
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. |
Line
| Prop | Type | Description |
|---|---|---|
dataKeyrequired | string | Key in data to use for y values |
yAxisId | string | number | Y-scale group id (Recharts `yAxisId`). Default: `"left"`. |
stroke | string | Stroke color. Default: var(--chart-line-primary) |
strokeWidth | number | Stroke width. Default: 2.5. Set to 1 for a lieflat-style "hairline" line. |
curve | CurveFactory | CurveAlias | Curve interpolation — a named alias (`"linear"` | `"monotone"` | `"natural"` | `"step"` | `"step-before"` | `"step-after"`, RM-112) or a raw `@visx/curve` factory. Default: `"monotone"`. `"natural"` (`curveNatural`, this package's pre-RM-112 default) is offered for parity only — Datawrapper's own guidance is to avoid natural/cardinal interpolation because it overshoots between two equal neighbours (`.claude/rules/charts.md` "Honesty"). |
animate | boolean | Whether to animate the line. Default: true |
fadeEdges | FadeEdges | Fade the line stroke toward transparent at the chart edges. - `true` fades both edges, `false` disables the fade entirely. - `"left"` / `"right"` fades only that side. Default: true |
showHighlight | boolean | Whether to show highlight segment on hover. Default: true |
showMarkers | boolean | Render scatter-style circle markers at each data point. Default: false |
markers | SeriesPointMarkerStyle | Marker styling (same options as Scatter). |
markerStyle | (d: Record<string, unknown>, index: number) => MarkerVariant | Per-point marker style — `"filled"` | `"hollow"` | `"none"`, decided per data point (RM-028). E.g. `(d) => (isWeekend(d.date) ? "hollow" : "filled")` — the lieflat "hollow dot = weekend, filled = weekday" idiom. Setting this renders a marker at every non-`"none"` point regardless of `showMarkers` — the per-point fill/stroke decision takes over from the shared marker style, while `markers.radius`/`ringGap`/`strokeWidth` still apply as the shared geometry. A hollow marker fills with `--chart-background` (the plot ground) so it stays theme-safe with no `dark:` override. Default: unset — no per-point styling, today's behaviour. |
symbols | SeriesSymbolsSpec | Placement/style wrapper over `showMarkers`/`markers` (RM-112, Datawrapper's line symbols: 8 shapes, first-and-last/all placement, filled/hollow style). Setting this turns markers on regardless of `showMarkers`. `placement` — `"all"` | `"ends"` | `"first"` | `"last"`, default `"ends"`. `style` — `"filled"` | `"hollow"`, default `"hollow"`. `shape`/`size` map onto `markers.shape`/`markers.radius`. Default unset: no symbols — today's behaviour, driven by `showMarkers`/`markers` alone. |
nulls | NullsMode | How this line draws a non-numeric sample (RM-112). Overrides the container-level `LineChart nulls` default. Unset — read the container default, or `"gap"` with no container default (a visible break, never a silent zero — the pre-RM-112 default was the "zero" behaviour, kept as `nulls="zero"`). |
outline | boolean | number | Paint a `--chart-background` halo stroke under the series stroke (Datawrapper's line outline, May 2024) so two crossing lines still read apart. `true` uses a 2px halo on each side; a number sets the halo width in px. Default `false` — no halo, today's behaviour. |
labelPeaks | LabelPeaksSpec | Label the top-k highest points on the line (RM-028) — lieflat's "top-2/top-3 peaks, enlarged and labelled" rule (L3 Barcode Lollipop). A bare number is the peak count at the default {@link DEFAULT_PEAK_MIN_GAP} -sample spacing; pass `{ count, minGap }` to override spacing. A peak within `minGap` samples of an already-picked (necessarily higher — peaks are picked highest-first) peak is skipped — "the barcode lesson: adjacent peaks must be forced apart" — so two close peaks label only the taller one. Uses the exported {@link spacedTopK} helper. Default: unset — no peak labels, today's behaviour. |
name | string | Series display name — the text of its end label, key item and auto summary (RM-110). Default: `dataKey`. |
seriesLabel | Responsive<SeriesLabelMode> | Where the series names itself (RM-110): `"end"` — a label at its last point (the chart reserves right margin for it); `"key"` — a swatch + name in a key row above the plot; `"none"`. Takes a `Responsive` value, e.g. `{ base: "end", narrow: "key" }`. Default: in a chart with two or more `Line`/`Area` series, a series whose `name` is set and differs from its `dataKey` gets `{ base: "end", narrow: "key" }` when a `ChartLegend` is composed, else `"end"`; every other series gets `"none"`. An explicit value always wins. Colliding end labels are nudged apart, or dropped and restated `sr-only`. |
valueLabels | ChartValueLabels | Automatic value labels (RM-110): `{ placement: "first" | "last" | "all" | "peaks", count?, outline?, matchColor?, format? }`. Placed with every other label through one collision pass — a label that cannot be placed is dropped and restated `sr-only`. `labelPeaks={n}` is the alias `{ placement: "peaks", count: n }` (exact, uncompacted numbers). |
dashFromIndex | number | Data index from which the line stroke becomes dashed (inclusive). Useful for projecting incomplete periods, e.g. dashed from yesterday through today. |
dashArray | string | Dash pattern for the tail segment when `dashFromIndex` is set. Default: "6,4" |
dashStroke | string | Stroke color for the dashed tail (from `dashFromIndex` on) when it should read as a distinct series from the solid head — e.g. a projection tail in a quieter ink than the actual-data head. Default: `stroke` (today's behaviour — head and tail share one ink, differentiated by dash alone). |
loading | boolean | Show the loading pulse overlay. Default: follows chart loading phase. Set `false` to disable even during loading. |
loadingStroke | string | Stroke color for the loading pulse overlay. Default: var(--chart-foreground) |
loadingStrokeOpacity | number | Loading pulse stroke opacity. Default: 0.5 |
loadingPulseMode | LineLoadingPulseMode | Override pulse animation mode (loop / exit / enter). |
onLoadingPulseCycleComplete | () => void | Called when a loop-mode pulse cycle completes. |
seriesIndex | number | Override the series index used for pattern/dash/marker differentiation under high decoration — needed when a single-series chart must differ from a sibling chart, e.g. sparklines. Does NOT affect layout. |
LineChartLoading
| Prop | Type | Description |
|---|---|---|
margin | Partial<Margin> | Chart margins |
stroke | string | Stroke color for the animated loading segment. |
strokeOpacity | number | Stroke opacity for the animated loading segment. Default: 0.5 |
gridStroke | string | Grid line stroke (color and opacity via color-mix or oklch alpha). |
gridShimmerStroke | string | Shimmer band stroke (color and opacity via color-mix or oklch alpha). |
gridShimmer | boolean | Animate a shimmer band across grid lines. Default: true |
gridShimmerLength | number | Shimmer band width in pixels. Default: 140 |
gridShimmerSpeed | number | Shimmer speed multiplier (higher = faster). Default: 1 |
gridShimmerSync | boolean | Match shimmer loop to the loading line pulse (cycle + inter-loop pause). |
label | string | Centered shimmer label text. Default: "Loading" |
aspectRatio | string | Aspect ratio as "width / height". Default: "2 / 1" |
plotHeight | Responsive<ChartPlotHeight> | The plot height, as on the chart it stands in for — px, `{ aspect }`, or per breakpoint. Wins over `aspectRatio`, so the placeholder holds the box the loaded chart will fill. |
className | string | Additional class name for the container |
LineLoadingPulseStroke
| Prop | Type | Description |
|---|---|---|
pathDrequired | string | |
mode | LineLoadingPulseMode | |
loopEpoch | number | Bumps to restart loop cycles without remounting the stroke. |
stroke | string | |
strokeOpacity | number | Stroke opacity for the animated segment. Default: 0.5 |
strokeWidth | number | |
onCycleComplete | () => void |
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`). |
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"`. |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| status | status="loading" → LineChartLoading skeleton; "ready" → the plot |