DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Line Chart

Token-driven line chart built on Recharts v3 — multi-series, step and curve interpolation, honest data gaps, y-axis domain control, threshold annotations and full light/dark theming.

Playground

Installation

pnpm add @tessinaui/ui recharts

Usage

import { LineChart } from "@tessinaui/ui";
import type { ChartConfig } from "@tessinaui/ui";

const config: ChartConfig = {
  revenue: { label: "Revenue", color: "var(--chart-1)" },
  expenses: { label: "Expenses", color: "var(--chart-3)" },
};

const data = [
  { month: "Jan", revenue: 4200, expenses: 2400 },
  { month: "Feb", revenue: 3800, expenses: 2210 },
  { month: "Mar", revenue: 5100, expenses: 2290 },
];

<LineChart
  data={data}
  config={config}
  categoryKey="month"
  dashedSeries={["expenses"]}
  label="Revenue outgrew expenses every month this quarter"
/>

Examples

Default

A smooth, single-series line.

Multiple

Two series in brand tones; expenses dashed so it stays distinct without colour.

Curve

All six interpolations, including the three step alignments.

Sizes

Three aspect ratios — sm (short/wide), md, and lg (tall).

Dots and labels

Marker modes, and the three ways to print values on the plot.

Missing data

What a null does, and what connectNulls does to it.

Y-axis domain

Anchored at zero, fitted to the data, or pinned to a fixed window.

Axes

Titles, tick formatters, a right-hand axis, and end-only ticks.

Thresholds and bands

ReferenceLine and ReferenceArea passed as children.

Forecast

Actual and projected as two series, the projection dashed.

Period comparison

The previous period as a muted, dashed ghost.

Series toggling

Toggles outside the legend, as real aria-pressed buttons.

Sparkline

The same component with its chrome switched off.

In a card

Range chips and the value callout belong to the card, not the chart.

States

Loading skeleton, empty state and error.

Missing data

A null leaves a gap, and that is the default. A gap is information: it says nobody measured this point.

connectNulls bridges the gap by drawing a straight segment between the two readings that surround it. That segment is an interpolation — a value nobody recorded, drawn in the same stroke as the data. Turn it on only when a gap genuinely means "unchanged since the last reading".

// Honest: Wednesday is missing, and the chart says so.
<LineChart data={readings} config={config} categoryKey="day" />

// Only when a gap really means "no change".
<LineChart data={readings} config={config} categoryKey="day" connectNulls />

Choosing a y-axis

The axis is anchored at zero by default. autoMinValue fits it to the data instead.

A truncated y-axis is legitimate on a line chart. A line encodes change, and a 4% movement on a zero-anchored axis is a flat line that tells the reader nothing. This is exactly the opposite of a bar chart, where length encodes magnitude and cropping the axis misstates the data.

Use minValue / maxValue when a chart must stay comparable across reloads, or when the scale is externally defined (a 0–100 score, a target band).

Non-colour differentiation

Colour alone is unreliable — for colour-vision-deficient readers, in greyscale print, and on a bad projector. LineChart gives you three encodings to layer on top of hue:

  • Markers — dots="all" gives each line a dotted trail; dots="last" marks only the current reading.
  • Dashes — dashedSeries={["expenses"]} renders named series with a dash pattern. This is also the convention for a projection or a previous period.
  • Direct labels — showLabels="endpoints" or "extremes" prints values on the plot, so a series can be read without consulting the legend at all.

Every series stroke is 2px, which is the minimum width for a chart mark to stay legible, and the --chart-* tokens are gated at 3:1 against the plot background.

Accessibility

  • Name the insight, not the anatomy. label becomes the chart's accessible name and renders it as a role="figure". "Visitors doubled since March" is useful; "Line chart of visitors by month" repeats what the page already says.
  • description adds a visually hidden summary, so a static read carries the message without anyone having to interact with the plot.
  • accessibilityLayer is on: the plot is keyboard-navigable point by point, and the tooltip is a polite live region.
  • Animation respects prefers-reduced-motion: reduce.

Composition

children are rendered inside the chart, so anything Recharts draws is available without a prop for each one — ReferenceLine, ReferenceArea, ReferenceDot, Brush, LabelList.

It is also the escape hatch for a fully custom tooltip: switch the built-in one off and pass your own.

<LineChart data={data} config={config} categoryKey="month" showTooltip={false}>
  <ChartTooltip content={<MyTooltip />} />
  <ReferenceLine y={target} label="Target" />
</LineChart>

Boundaries

  • Range presets (1D/1W/1M/1Y), the big value callout, and a granularity switcher belong to the surrounding card, not to the chart. No surveyed design system ships them as chart props — see the "In a card" example.
  • A filled area under the line is AreaChart, not a prop here.
  • A single value against a target is Meter or Progress.
  • Categorical comparison is BarChart; a line implies a continuous axis.

Theming

ElementToken
Series stroke / marker--chart-1 … --chart-6 (via config[key].color)
Grid + axis linesborder
Axis tick labels and titlesmuted-foreground
Printed value labelsforeground
Tooltip surfacepopover / popover-foreground / border

Core props

PropTypeDefaultDescription
dataRecord<string, string | number | null>[]—Row-per-category data.
configChartConfig—Series metadata (label, color).
categoryKeystring—Datum key for the x-axis.
seriesstring[]all config keysWhich series to plot.
curve"monotone" | "natural" | "linear" | "step" | "stepBefore" | "stepAfter""monotone"Interpolation.
size"sm" | "md" | "lg""md"Aspect ratio.
dots"none" | "all" | "last""all"Point markers. "last" marks each series' final reading.
showLabelsboolean | "endpoints" | "extremes"falsePrint values on the plot.
dashedSeriesstring[][]Series rendered with a dashed stroke.
connectNullsbooleanfalseBridge null gaps with an interpolated segment.
autoMinValuebooleanfalseFit the y-axis to the data instead of anchoring at zero.
minValue / maxValuenumber—Pin either end of the y-axis.
yAxisPosition"left" | "right"follows dirWhich side the y-axis sits on.
xAxisLabel / yAxisLabelstring—Axis titles.
xTickFormatter / yTickFormatter(value, index) => string—Format tick text.
startEndOnlybooleanfalseRender only the first and last x ticks.
showGrid / showLegend / showXAxis / showYAxis / showTooltipboolean—Toggle chart chrome.
labelstring—Accessible name; renders as role="figure".
descriptionstring—Visually hidden summary.
childrenReactNode—Extra Recharts children (ReferenceLine, Brush, …).
dir"ltr" | "rtl""ltr"Reading direction.
labelstring—Accessible name; renders the chart as a role="figure".
descriptionstring—Visually hidden summary for assistive tech.
loadingbooleanfalseRender the skeleton.
errorReactNode—Render an error message instead of the chart.
emptyStateReactNode"No data to display"Shown when data is empty.

Migration

connectNulls now defaults to false. A chart whose data contains null previously drew straight through the gap; it now breaks the line there. Pass connectNulls to restore the old rendering — but read "Missing data" above first, because the bridged segment is a value nobody measured.

showDots is deprecated in favour of dots. showDots keeps working for one minor and warns once in development; dots wins if both are set.

BeforeAfter
showDotsdots="all" (the default)
showDots={false}dots="none"
LabelLink

On this page

PlaygroundInstallationUsageExamplesDefaultMultipleCurveSizesDots and labelsMissing dataY-axis domainAxesThresholds and bandsForecastPeriod comparisonSeries togglingSparklineIn a cardStatesMissing dataChoosing a y-axisNon-colour differentiationAccessibilityCompositionBoundariesThemingCore propsMigration