Line chart

A multi-series line chart with a gliding crosshair, legend toggles, and paths that morph between ranges.

pnpm dlx shadcn@latest add @uiarc/line-chart
Live · keyboard ready

Trial signups

Total signups6,670

+20.6% vs previous 30 days

Trial signups, Sun, Aug 23 to Today, Sep 21. This period, latest 248. Previous period, latest 183.

Trial signups
DateThis periodPrevious period
Sun, Aug 23128121
Mon, Aug 24198202
Tue, Aug 25192198
Wed, Aug 26211190
Thu, Aug 27206182
Fri, Aug 28199172
Sat, Aug 29130121
Sun, Aug 30159134
Mon, Aug 31254218
Tue, Sep 1262217
Wed, Sep 2247226
Thu, Sep 3260223
Fri, Sep 4253231
Sat, Sep 5169146
Sun, Sep 6158121
Mon, Sep 7231184
Tue, Sep 8239203
Wed, Sep 9437200
Thu, Sep 10338181
Fri, Sep 11240187
Sat, Sep 12139118
Sun, Sep 13134148
Mon, Sep 14250235
Tue, Sep 15268211
Wed, Sep 16262230
Thu, Sep 17260226
Fri, Sep 18268232
Sat, Sep 19154143
Sun, Sep 20176147
Today, Sep 21248183

  • Traffic, revenue, or latency over days or weeks in a dashboard card.
  • Comparing this period against a previous period or target with a dashed series.
  • Charts that switch ranges (7d, 30d, 90d) and should morph rather than redraw.
  • Use sparkline for a tiny inline trend with no axes or tooltip.
  • Use bar-chart for discrete categories or counts per bucket.
  • Use donut-chart when the question is the share of a whole, not change over time.

Installation

Add Line chart with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/line-chart

Adds the component and its local dependencies, and installs motion. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { LineChart } from "@/registry/components/line-chart/line-chart"; const data = [  { key: "2026-09-01", label: "Tue, Sep 1", axisLabel: "Sep 1", values: { signups: 120, previous: 98 } },  { key: "2026-09-02", label: "Wed, Sep 2", values: { signups: 142, previous: 104 } },  { key: "2026-09-03", label: "Thu, Sep 3", axisLabel: "Sep 3", values: { signups: 131, previous: 110 } },]; export function SignupsChart() {  return (    <LineChart      label="Signups"      data={data}      series={[        { key: "signups", label: "This period" },        { key: "previous", label: "Previous period", dashed: true },      ]}    />  );}

Headline readout driven by the crosshair

example.tsx
const [active, setActive] = useState<LineChartDatum | null>(null); <p>{(active ?? data[data.length - 1]).values.signups}</p><LineChart label="Signups" data={data} series={series} onActiveChange={(_, datum) => setActive(datum)} />

LineChart

An SVG line chart with a scrubbable crosshair, series toggles, morphing data changes, and a screen reader data table.

PropTypeDefaultDescription
dataRequiredLineChartDatum[]–Points: { key, label, axisLabel?, values }. values maps series keys to numbers; a missing value counts as zero.
seriesRequiredLineChartSeries[]–Lines: { key, label, color?, dashed?, area? }. area defaults to true for the first series.
labelRequiredstring–What is measured, such as "Signups". Names the chart for assistive technology.
unitstring""Unit after each value in the tooltip and table, such as "ms".
heightnumber220Plot height in pixels. The width follows the container.
formatValue(value: number, series: LineChartSeries) => string–Formats values in the tooltip and data table. Defaults to grouped numbers with one decimal.
formatTick(value: number) => stringcompact formatFormats the value axis, such as 1.2K.
hiddenSeriesstring[]–Controlled hidden series keys.
defaultHiddenSeriesstring[]–Hidden series keys on first render when uncontrolled.
onHiddenSeriesChange(hidden: string[]) => void–Called when a legend toggle changes the hidden keys.
onActiveChange(index: number | null, datum: LineChartDatum | null) => void–Called as the crosshair moves, and with null when it leaves. Use it to drive a headline readout.
loadingbooleanfalseDims the current lines while the next range loads, or shows a skeleton when there is no data.
emptyLabelstring"No data for this range"Shown when there are no points.
legendboolean–Series toggles above the plot. Shown by default when there is more than one series.
categoryLabelstring"Date"Header of the first column in the screen reader table, and part of the plot's slider label.
curve"smooth" | "linear""smooth"Monotone curves that never swing past the data, or straight segments.
refRef<HTMLElement>–The figure element.
classNamestring–Class on the figure.
ArrowLeftorArrowDown
Moves the crosshair to the previous point.
ArrowRightorArrowUp
Moves to the next point; from rest it starts on the latest point.
PageUporPageDown
Jumps about a sixth of the range.
HomeorEnd
Jumps to the first or last point.
Escape
Lets go of the crosshair.
  • The figure is named by label; the plot is a role="slider" whose aria-valuetext reads the date and every visible series value.
  • A visually hidden summary gives the range and latest values, and a hidden table lists every point for screen readers.
  • Legend toggles are buttons with aria-pressed; a hidden live region says Loading while loading is true.
  • The SVG and tooltip are aria-hidden, so values are never read twice.
  • Lines draw left to right the first time the chart scrolls into view.
  • New data morphs every line from the shape on screen, sampled on shared x positions, and the value scale springs to its new range while gridlines slide.
  • A hidden series flattens into the baseline as it fades; the crosshair glides between points and the tooltip follows on its own spring, flipping sides near the edge.
  • Reduced motion skips the draw, morphs, and glides, and stops the loading pulse.
  • Width follows the container through a ResizeObserver; the plot redraws in its own pixels, and axis labels thin to one per 76px.
  • Touch scrubs with pointer capture and releases on lift; touch-action: pan-y keeps vertical page scroll working over the plot.
  • A mouse scrubs on hover and releases on leave. The value gutter is a fixed 48px column.
  • Paths are written straight to the DOM from motion values, not React state, so scrubbing and morphs do not re-render the tree.
  • Smooth curves are sampled up to 16 points per segment (about 192 per line); keep data to a few hundred points per series.
  • One ResizeObserver per chart; the draw-in waits for useInView.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for one to three measures over time when the trend shape matters. For category comparison use bar-chart; for parts of a whole use donut-chart.
  • Keep series and datum keys stable across range changes so lines morph instead of redrawing.
  • Set axisLabel only on the dates you want on the axis; the chart thins them further to one per 76px, always keeping the newest.
  • Drive a headline number from onActiveChange, as in metric-card or stat-card layouts.

The full library index for assistants is at /llms.txt.