Skip to content

Combo Chart component

Chart covers nine chart types (line, area, bar, column, pie, donut, heatmap, sparkline, combo) behind one component and a row-based data API — hand it an array of plain objects plus which fields are the category and the series, and it figures out the rest. Every data point is keyboard-reachable and announces its value, not just hover-only like most charting libraries.

Data Visualization35 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

Row-based data

The common path: pass data as a plain array of objects, x as the field holding the category (a date, a label), and series for the field(s) to plot. No need to pre-shape data into a categories/series structure — Chart derives it from your rows.

<Chart
  type="line"
  title="Monthly signups"
  data={[
    { month: "Jan", signups: 42 },
    { month: "Feb", signups: 58 },
    { month: "Mar", signups: 51 },
    { month: "Apr", signups: 67 },
  ]}
  x="month"
  series="signups"
/>

Multiple series

series accepts an array for a multi-line/multi-bar chart — pass an array of accessors (or ChartSeriesDef objects with their own label/color) and Chart plots each one, auto-enabling the legend once there's more than one.

<Chart
  type="bar"
  data={salesData}
  x="quarter"
  series={[
    { key: "revenue", label: "Revenue" },
    { key: "cost", label: "Cost" },
  ]}
/>

Projected/forecast segments

A series entry can mark itself isProjection to render as a distinct dashed segment — for a forecasted continuation of real data (actuals through today, a projected trend afterward) without needing two separate charts stitched together. Bar and column charts split the same way, rendering projected bars with their own visual treatment on the same axis scale as the real values.

<Chart
  type="line"
  data={[...actuals, ...forecast]}
  x="month"
  series={[
    { key: "revenue", label: "Actual" },
    { key: "projectedRevenue", label: "Forecast", isProjection: true },
  ]}
/>

Axis, legend, and tooltip options

axis/axisX/axisY control tick visibility and formatting independently per axis; legend and tooltip each accept either a boolean to toggle them outright or an options object for finer control (position, formatting). dataFormat applies a shared prefix/suffix/decimal-place format across labels and tooltips at once, for currency or percentage values.

<Chart
  type="column"
  data={data}
  x="month"
  series="revenue"
  dataFormat={{ prefix: "$", decimals: 0 }}
  legend={{ position: "bottom" }}
/>

Zoom, export, and fullscreen

Three opt-in interaction features, each disabled by default: zoom enables axis zoom/pan (mode: "x" for horizontal-only, "xy" for both, plus wheel/pan toggles); export adds an action that downloads the chart as SVG or PNG; fullscreen adds a toggle that expands the chart to fill the viewport. All three default to false/off, since not every chart context wants the extra chrome.

<Chart
  type="line"
  data={data}
  x="date"
  series="value"
  zoom={{ mode: "x" }}
  export={{ formats: ["png", "svg"] }}
  fullscreen={{ enabled: true }}
/>

Sparklines and combination charts

type="sparkline" is a small trend for a table cell or a stat card: no axes, grid, legend, title or frame, 40 px high, with a dot on the last point. sparkline={{ kind }} makes it a line, an area or columns. type="combo" draws columns, lines and areas together: each series says type ("column", "line" or "area") and axis ("left" or "right"), and a series on the right gets its own scale through axisY2, so a rate can sit over amounts.

<Chart type="sparkline" data={week} x="day" series="sales" ariaLabel="Sales this week" />

<Chart
  type="combo"
  data={rows}
  x="month"
  series={[
    { key: "revenue", name: "Revenue" },
    { key: "rate", name: "Conversion (%)", type: "line", axis: "right" },
  ]}
  axisY={{ title: "Revenue" }}
  axisY2={{ title: "Conversion (%)" }}
/>

Targets, dates and stacked areas

referenceLines draws a labelled line at a value (a goal, a limit, an average) across a line, area, column or bar chart, and the scale grows to include it. When every x value is a Date, a line or area chart places each point where its date falls, so a month-long gap looks like one (axisX.scale: "category" spaces points evenly instead). A stacked area (line.area.stacked) stacks the series so the top edge is the total, and tooltip.showTotal adds it up. axisX and axisY accept a title.

<Chart
  type="area"
  data={data}
  x="date"
  series={["web", "mobile"]}
  line={{ area: { stacked: true } }}
  referenceLines={[{ value: 5000, label: "Target", dashed: true }]}
  axisY={{ title: "Sessions" }}
/>

Screen readers, translation and large data

Every chart carries a visually hidden table of its values (turn it off with dataTable={false}), keyboard arrows move between points, and touch and mouse use the same tooltip. labels translates or rewords the texts the chart adds (export, fullscreen, reset zoom, loading, no data). Series beyond the seven base colours reuse the hues in lighter and darker shades, so two lines are never the same colour, and exports keep the real colours and background. Pointing at 16,000 points stays fast because lines and labels are not redrawn on each move.

Loading, error, and empty states

isLoading, error, and empty each swap in a dedicated state in place of the plotted chart — pass them straight from whatever data-fetching hook is feeding the chart's data, instead of conditionally rendering Chart itself and losing its layout/sizing while the state changes.

<Chart type="line" data={data ?? []} x="month" series="value" isLoading={isLoading} error={fetchError && "Couldn't load data."} />

Props

The full public API for Combo Chart. Every prop is stable and intentionally typed.

Content

data

Row[] | { categories, series }

default: —

Row-based data array, or explicit categories/series (required).

x

keyof Row | ((row: Row) => ChartCategory)

default: —

Category accessor, row-mode only.

series

ChartSeriesDef<Row> | ChartSeriesDef<Row>[]

default: —

Series accessor(s), row-mode only.

title / description

ReactNode

default: —

Chart heading text.

ariaLabel

string

default: —

Accessible label for the chart.

dataFormat

ChartDataFormatOptions

default: —

Value formatting (prefix/suffix/decimals) for labels/tooltips.

showLabels

ChartShowLabelsOptions

default: —

Inline value-label visibility.

labels

Partial<ChartLabels>

default: English

Translate or reword every text the chart adds: export, fullscreen, resetZoom, loading, noData, keyboardHint, filterAll, total and the data table headings.

Appearance

type

"line" | "area" | "bar" | "column" | "pie" | "donut" | "heatmap" | "sparkline" | "combo"

default: —

Chart type (required).

background

string

default: —

Chart background color.

unstyled

boolean

default: false

Drop the built-in <figure> card frame (border / padding / radius / surface) — for a chart nested in a Card or a sparkline slot.

palette

{ colors?; colorShade?; roles? }

default: —

Series color palette overrides.

line / bar / column / pieStyle / pie / heatmap

type-specific options

default: —

Per-type rendering options. pie: { center?: false, renderCenter?: ({total, formatted}) => ReactNode } for the donut hole. heatmap: { color, minOpacity, maxOpacity, domain, cellGap, cellRadius, showValues, emptyColor }.

sparkline

{ kind?: "line" | "area" | "column" }

default: { kind: "line" }

type="sparkline" only: the shape of the small trend.

Layout & positioning

axisY2

ChartAxisOptions

default: —

type="combo": the right-hand value axis (show, tickCount, title) for series with axis: "right".

height

number

default: 320

Chart height in pixels.

padding

{ top?; right?; bottom?; left?: number }

default: —

Plot area padding.

axis / axisX / axisY

ChartAxisOptions

default: —

Axis visibility, ticks, title (drawn under the axis) and, on axisX, scale (auto, category or time). axisX/axisY take { show?, hide?, tickCount? } — `{ hide: true }` is an alias for `{ show: false }` (an explicit `show` wins). A custom `padding` on a labelled bar/column axis is floored so labels don't vanish.

grid

ChartGridOptions

default: —

Gridline configuration.

Behavior & states

referenceLines

{ value, label?, color?, dashed? }[]

default: —

Lines across a line, area, column or bar chart at fixed values (a target, a limit). The scale makes room for them.

dataTable

boolean

default: true

Adds a visually hidden table of the values, so a screen reader can read the numbers.

legend

boolean | ChartLegendOptions

default: true for multi-series

Legend visibility/position.

tooltip

boolean | ChartTooltipOptions

default: true

Hover/focus tooltip visibility and formatting.

zoom

false | { mode?: "x" | "xy"; wheel?; pan? }

default: —

Enables zoom/pan on axis charts.

export

false | { formats?: ("svg" | "png")[]; filename? }

default: —

Enables an export action (SVG/PNG).

fullscreen

false | { enabled?: boolean }

default: —

Enables a fullscreen toggle action.

filters

false | ChartFiltersOptions<Row>

default: —

Enables interactive data filters.

isLoading

boolean

default: —

Shows a loading state instead of the chart.

error

ReactNode

default: —

Shows an error state instead of the chart.

empty

ReactNode

default: —

Shown when data resolves to nothing plottable.

Events

onPointClick

(event: ChartPointEvent<Row>) => void

default: —

Called when a data point is clicked or activated via keyboard.

onCellClick

(event: ChartHeatmapCellEvent<Row>) => void

default: —

type="heatmap" — a cell was clicked or keyboard-activated.

onLegendChange

(hiddenSeriesIds: string[]) => void

default: —

Called when series visibility toggles via the legend.

onSelectionChange

(selection: ChartSelection) => void

default: —

Called when a zoom/brush selection changes.

Styling

className / classNames / style

string / ChartClassNames / CSSProperties

default: —

Styling overrides (classNames covers base/header/title/description/actions/stage/svg/legend/filters).

Accessibility

  • Every data point is keyboard-reachable — Tab/arrow-key navigation moves between points, and onPointClick fires the same way for a keyboard activation (Enter/Space) as it does for a mouse click.
  • Pass ariaLabel describing what the chart shows ("Monthly signups by quarter") — a chart with no accessible name is announced generically, which tells a screen reader user nothing about its content.
  • title/description render as real visible text (not just a tooltip), doubling as a readable summary for anyone who can't perceive the plotted shape itself.
  • The legend, when present, is independently keyboard-operable — toggling a series's visibility via onLegendChange works the same from keyboard as from a mouse click on the legend swatch.

Best practices

  • Use dataFormat once for consistent number formatting across labels/tooltips instead of pre-formatting values in your own data — keeping raw numbers in data lets Chart handle formatting, sorting, and axis scale correctly.
  • Only enable zoom for charts genuinely long enough to benefit from it — dense time-series data, mostly. Turning it on for a 4-bar comparison chart just adds interaction surface with nothing to zoom into.
  • Reach for pie/donut only for a small number of categories (roughly five or fewer) where part-to-whole is the actual point — beyond that, a bar chart reads more accurately and doesn't force awkward slice-color distinctions.
  • Use isProjection for genuinely forecasted/estimated data, not just to stylistically differentiate two real series — the dashed treatment specifically communicates "this part isn't actual measured data."
  • Always pass title or ariaLabel (ideally both) — an unlabeled chart is one of the least accessible patterns in a data-heavy UI.