DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
ChartArea ChartBar ChartLine ChartLive Line ChartComposed ChartPie ChartFunnel ChartRadar ChartRadial ChartSankey ChartScatter ChartCandlestick ChartSunburst ChartChoropleth ChartActivity Heatmap
AI agentsContributing
  1. Components
  2. Composed Chart

Composed Chart

Bars, lines and areas on one shared axis, built on Recharts v3 — one hue, with the mark telling the series apart, and a second value axis for a series in another unit.

$ pnpm add @tessinaui/ui

When to use

  • Two readings of the same thing belong in one plot. A total as bars with its average, target or previous period as a line: the reader compares the two at each category without looking back and forth.
  • A level and the flow that moves it. Tickets opened and resolved against the open backlog; money in and out against the balance.
  • A measured past and a projected future. Bars for what happened, a dashed line for what is expected, a band for how sure you are.
  • Two units, when the relationship is the point. Revenue with a conversion rate. Bind the second series to the second axis and title both.

When not to use

  • For one kind of mark. Use BarChart, LineChart or AreaChart. Each has options this chart leaves out: horizontal bars, value labels, stacked areas, a fitted axis.
  • For two unrelated measures. Two scales invite a reader to compare heights that mean nothing against each other. Stack two charts that share a category axis instead. On a phone this is nearly always the better answer.
  • For more than two lines. In one hue, a third line cannot be told from the first. Split the chart.
  • For parts of one whole with no time axis. Use PieChart.
  • For a handful of exact figures. A Table reads faster than marks when the reader needs the numbers, not the shape.

Installation

Terminal
pnpm add @tessinaui/ui recharts

Usage

example.tsx
import { ComposedChart } from "@tessinaui/ui";
import type { ComposedChartConfig } from "@tessinaui/ui";

const config: ComposedChartConfig = {
  revenue: { label: "Revenue", type: "bar" },
  average: { label: "3-month average", type: "line" },
};

const data = [
  { month: "Jan", revenue: 42, average: 41 },
  { month: "Feb", revenue: 38, average: 40 },
];

<ComposedChart
  data={data}
  config={config}
  categoryKey="month"
  label="Revenue has held at or above its three-month average"
/>

Each series names its mark with type. A series with no color takes one hue, the first chart colour, stepped in lightness for bars. Pass color to choose your own.

Examples

Default

Bars with a line in the same unit, on one axis. The line is drawn over a hairline in the colour behind the chart, so it stays a line where it crosses the bars, and the legend keys each series with the shape of its mark.

A second value axis

axis: "secondary" measures a series on a second scale at the far side of the plot. Both axes start at zero, so their ticks share the grid lines. One valueFormatter spells the tooltip and both axes, and the axis titles tell a reader which scale belongs to which series.

Bars, a line and an area

All three marks on one axis. The paint order is fixed: areas behind bars, bars behind lines, whatever order the config lists them in.

Stacked bars with a line

stacked stacks the bar series. Lines and areas never stack, so a line is read against the top of the stack. Only the outermost segment rounds.

Grouped bars with a line

Two or more bar series sit side by side by default. maxBarWidth keeps a chart with few categories from drawing slabs.

Two lines

Two tones of one hue do not tell two lines apart, so the second line dashes itself, on the plot and in the legend. dashedSeries chooses which series dash; pass [] for none.

Curve and markers

curve sets the interpolation for lines and areas. dots puts a marker on every reading, on the latest one, or on none.

Forecast

Bars for what was measured, a dashed line for what is projected, and a band for the likely range. An area whose value is a [low, high] pair draws the band. A null leaves a gap.

Negative values

Bars below zero grow downward and round their lower end. A ReferenceLine at zero draws the baseline they cross.

Rounded

rounded sets the bars' corners on the same scale as the rest of the library. The legend key follows.

Reference lines and bands

Annotations compose as children. One measured on the second axis names it with yAxisId="secondary"; without it, an annotation binds to the primary axis.

Range selector

A Brush composes as a child and takes the chart's inks. Reach for it when a reader needs an arbitrary window over a long series. For a few fixed windows (a week, a month, a year), a row of range buttons above the chart is quicker to use and easier on a phone.

Interactive legend

Legend interactivity is controlled: the chart never hides a series on its own. A hidden series stays in the legend as an unpressed toggle, so it can be switched back on.

In a narrow card

The legend and the category axis take their height out of the chart's box. In a narrow column, give the box the tall ratio, keep two ticks and drop the value axis.

Accessibility

Choosing a month

With selectionMode="single" a click or a tap over a month, or Enter or Space on the month the arrow keys are on, chooses it; choosing it again clears it. The chosen period keeps the hover band, each line's dot on it wears a ring, and its tick becomes the inverse chip, so the choice reads without colour. The chip takes the bars' corner from rounded. Here the readout above the chart follows the chosen month.

On a coloured plate

Inside a coloured Surface the chart follows the plate with no prop. Series with no color take the plate's ink, the line's casing takes the plate's colour, and the ticks, grid, legend and focus ring follow. The tooltip is a page panel, so it keeps the page's look.

States

Loading, empty and error side by side.

  • With selectionMode="single" the periods become a list to choose from. The chart's one tab stop is a named listbox ("Revenue: Periods"), each period an option with aria-selected and a name that carries its readings, and the period the arrow keys are on is its aria-activedescendant. Enter or Space chooses that period, so Enter no longer toggles the tooltip; Escape still hides it. A polite status region says "Feb selected" or "Selection cleared" once per change. The screen reader pass of this mode is still to be run.

API Reference

ComposedChart

PropTypeDefaultDescription
dataComposedChartDatum[]—Row-per-category data. An area's value may be a [low, high] pair.
configComposedChartConfig—Series settings keyed by data key: label, type, and optionally axis, color or theme, icon. Declaration order is legend order.
categoryKeystring—Datum key for the category axis. Warns in development if it is not a key of the first row.
seriesstring[]all config keysWhich series to plot, and in what order.
labelstring—Accessible name; renders the chart as a role="figure".
descriptionstring—Visually hidden summary for assistive tech.
size"sm" | "md" | "lg""md"Aspect ratio (3/1, 2/1, 16/9).
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner rounding of each bar's growing end. full is a pill.
stackedbooleanfalseStack the bar series. Lines and areas never stack.
maxBarWidthnumber—Cap on bar thickness in px.
barGapnumber4Gap between bars within a category group.
categoryGapnumber | string"10%"Gap between category groups.
curve"linear" | "natural" | "monotone" | "step" | "stepBefore" | "stepAfter""monotone"Interpolation for lines and areas.
dots"all" | "none" | "last""all"Point markers on lines.
dashedSeriesstring[]second line, second areaSeries drawn with a dashed stroke. [] for none.
connectNullsbooleanfalseDraw across null values instead of leaving a gap.
fillOpacitynumber0.25Opacity of an area's wash.
showGridbooleantrueHorizontal grid lines.
showXAxisbooleantrueThe category axis.
showYAxisbooleantrueThe value axes.
showLegendbooleantrueThe legend.
legendPosition"top" | "bottom""bottom"Legend placement.
showTooltipbooleantrueThe tooltip.
hiddenSeriesstring[]—Series to hide. They stay in the legend as unpressed toggles.
onSeriesToggle(key: string) => void—Called when a legend item is pressed.
xAxisLabelstring—Title of the category axis.
yAxisLabelstring—Title of the primary value axis.
secondaryYAxisLabelstring—Title of the second value axis.
valueFormatter(value, seriesKey) => string | number—Formats values in the tooltip and on both value axes.
xTickFormatter(value, index) => string—Formats category tick labels.
startEndOnlybooleanfalseDraw only the first and last category ticks.
animatebooleantrueAnimate the marks. prefers-reduced-motion turns it off regardless.
dir"ltr" | "rtl"inheritedReading direction. Unset, read off the chart's position in the page once it mounts.
loadingbooleanfalseRender the skeleton, sized to the data.
errorReactNode—Render an error message with the error intent.
emptyStateReactNode"No data to display"Shown when data is empty.
selectionMode"none" | "single""none""single" lets a reader choose one period by click, tap, Enter or Space; choosing it again clears it.
selectedstring | null—The categoryKey value of the chosen period, controlled. null chooses nothing.
defaultSelectedstring | nullnullThe categoryKey value of the period chosen at first render, uncontrolled.
onSelectedChange(key, { datum, index, source }) => void—Called on every choice and clear; source is "pointer" or "keyboard".
activestring | null—The categoryKey value of the lit period, controlled: opens the tooltip on it.
onActiveChange(key: string | null) => void—Called when the chart's own pointer or arrow keys move to another period.
stringsPartial<ComposedChartStrings>English, Arabic under dir="rtl"Selection's words per entry: selected, cleared, items, period.
childrenReactNode—Extra Recharts elements drawn in the plot (ReferenceLine, ReferenceArea, Brush).

Series config

FieldTypeDefaultDescription
type"bar" | "line" | "area"—The mark. Required. A series with any other value is not drawn, and warns in development.
axis"primary" | "secondary""primary"Which value axis the series is measured on.
labelReactNode—Name in the legend and the tooltip.
colorstringthe house rampAny CSS colour. Unset, bars step through three tones of the first chart colour and every line and area takes its base.
theme{ light: string; dark: string }—A colour per theme, instead of color.
iconComponentTypethe mark's shapeReplaces the legend and tooltip key.

Data attributes

AttributeOnValue
data-component="composed-chart"rootAlways. The base keeps data-slot="chart" on the same element, so target this to tell a composed chart from the other chart types.
data-chartrootGenerated chart id, used to scope the per-series CSS variables.
data-slot="composed-chart-key"legend, tooltipThe key drawn for a series. data-type is its mark.
data-slot="composed-chart-casing"plotThe hairline under a line that crosses bars.
data-slot="composed-chart-last-dot"plotThe marker dots="last" draws.
data-slot="chart-tooltip"tooltipWhile a tooltip is shown.
data-slot="chart-legend" / chart-legend-itemlegendLegend items are real aria-pressed buttons when onSeriesToggle is set.
data-slot="chart-skeleton"rootWhile loading.
data-slot="chart-message"rootWhile error or empty.

Accessibility

Screen readers: untested. No VoiceOver, NVDA or JAWS pass has been recorded for this component yet.

  • label is the only way to name the chart. It renders the container as a role="figure" region with that accessible name. An aria-label on the root is silently ignored, because the root carries no role for a label to attach to.
  • Say what the chart shows, not what it is. "Revenue has held above its average since March" beats "Bar and line chart of revenue by month".
  • description renders a visually hidden summary inside the figure, so a static read carries the numbers without stepping through the plot.
  • Offer the rows as a table beside a chart with two axes. It is the reading that works at any zoom and with any assistive technology.
  • Keyboard: Tab moves to the chart, ← / → step through categories and move the tooltip, and Escape dismisses the tooltip without moving focus (WCAG 1.4.13). Legend toggles are buttons in the tab order before the plot. A Brush handle is a slider: Tab to it and the arrow keys move it.
  • Colour is never the only channel (WCAG 1.4.1). Series differ by mark, by dash, and by the shape of their legend key.
  • Contrast (WCAG 1.4.11): each mark is held to 3:1 against the surface. Two tones of one hue cannot also reach 3:1 against each other, which is why a line carries a casing and a stack carries a seam.
  • Touch: legend toggles take a 44px target on a touch pointer.

Do / Don't

  • Do title both axes when you use the second one. Without a hue to tie a series to its scale, the title is the link.
  • Do keep the two axes starting at zero. A fitted axis beside a zero-based one exaggerates whichever series it holds.
  • Do list series in the order a reader should meet them: the legend follows the config.
  • Don't plot a ratio that spikes on the same chart as the total it is a ratio of: one outlier flattens every other reading on its axis.
  • Don't add a third line. Split the chart, or move a series to bars.
  • Don't use the second axis to line two series up so they look related. If the scales are yours to choose, so is the story.

Agent notes

  • type goes in config, per series. There is no barSeries / lineSeries prop and no child components: config={{ revenue: { label: "Revenue", type: "bar" } }}.
  • Name the chart with label, not aria-label. aria-label reaches the root element, but the root has no role, so assistive tech ignores it.
  • Leave color out unless the design calls for a specific colour. The default is one hue in tones, and it follows a coloured Surface on its own.
  • The second axis is axis: "secondary" on the series, not a prop on the chart. An annotation on that axis needs yAxisId="secondary".
  • stacked is a boolean and stacks bars only. There is no normalized stack here; use BarChart for one.
  • rounded is a token scale (none…full), not a pixel number.
  • There is no horizontal orientation and no value labels. Use BarChart or LineChart when you need either.
  • categoryKey must be a key of the data rows. A typo renders a chart with a blank category axis and warns in development.

Theming

ElementToken
Bars, with no color--chart-1 in three tones: lighter, base, darker
Lines and areas, with no color--chart-1
Series on a coloured plate, with no color--surface-ink; the first bar series at 80% of it
Line casing, marker ring, stacked segment seam--surface-fill, the colour behind the chart
Area washthe series colour at fillOpacity
Grid + axis linesmuted-foreground/20
Axis tick labels and titlesmuted-foreground
Brush frame, range and handlesthe grid and tick inks
Tooltip surfacepopover / popover-foreground / border
Live Line ChartPie Chart

On this page

When to useWhen not to useInstallationUsageExamplesDefaultA second value axisBars, a line and an areaStacked bars with a lineGrouped bars with a lineTwo linesCurve and markersForecastNegative valuesRoundedReference lines and bandsRange selectorInteractive legendIn a narrow cardAccessibilityChoosing a monthOn a coloured plateStatesAPI ReferenceComposedChartSeries configData attributesAccessibilityDo / Don'tAgent notesTheming