+14.5% vs. last month
const chartData = /* ... */;
const current = /* ... */;
const previous = /* ... */;
const delta = /* ... */;
const endDot = /* ... */;
const currency = /* ... */;
const percent = /* ... */;
<Card className="w-full max-w-64">
<CardHeader>
<CardDescription>Monthly revenue</CardDescription>
<CardTitle className="text-2xl">{currency.format(current)}</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Sparkline
data={chartData}
x="month"
y="revenue"
mode="area"
fill="gradient"
height={48}
marks={endDot}
ariaLabel="Monthly revenue over the last six months"
/>
<p className="text-xs text-fg-muted">
<span className="font-medium text-fg-success">
{percent.format(delta)}
</span>{' '}
vs. last month
</p>
</CardContent>
</Card>A sparkline is one series and nothing else — no axes, no grid, no legend, no tooltip. It belongs inside a stat card, a table cell, or a list row, where the number is the message and the line is the context. Everything else about it is a normal chart: same palette, same animation, same keyboard focus.
Installation
npx shadcn@latest add @dotui/chart-sparklineIt brings the chart core — the host, the palette, and the shared frame — along with it.
Usage
import { Sparkline } from '@/ui/chart-sparkline'Point x and y at fields of your rows. There is no config object and no series list — a sparkline draws exactly one series.
const data = [
{ day: 'Mon', visitors: 186 },
{ day: 'Tue', visitors: 205 },
{ day: 'Wed', visitors: 173 },
{ day: 'Thu', visitors: 241 },
]
export function Example() {
return (
<Sparkline
data={data}
x="day"
y="visitors"
ariaLabel="Visitors over the last four days"
/>
)
}Size and chrome
height defaults to 40 — short enough to sit under a headline number, tall enough to show a shape. Width fills the container. The line spans the box edge to edge, inset only by half its own stroke, so a sparkline lines up with the text above it.
<Sparkline data={data} x="day" y="visitors" height={56} ariaLabel="Visitors" />Pass mode="area" to fill under the line, and fill="gradient" to fade that fill out downward:
<Sparkline data={data} x="day" y="visitors" mode="area" fill="gradient" />The tooltip is off by default, since a sparkline is usually read as a whole rather than point by point. Turn it on with tooltip:
<Sparkline data={data} x="day" y="visitors" tooltip />Color
color takes any CSS color and paints both the stroke and the fill. It defaults to the first palette slot, var(--chart-1). Because it is a plain string, a sparkline can encode its own direction:
const first = data[0]?.visitors ?? 0
const last = data.at(-1)?.visitors ?? 0
const up = last >= first
<Sparkline
data={data}
x="day"
y="visitors"
color={up ? 'var(--color-success)' : 'var(--color-danger)'}
ariaLabel="Visitors"
/>Annotations
Anything beyond the line is a mark you pass yourself: marksBefore paints under the series, marks over it. A dashed average line and a dot on the latest point are the two most common:
import { dot } from '@tanstack/charts/dot'
import { ruleY } from '@tanstack/charts/rule'
const mean = data.reduce((total, row) => total + row.visitors, 0) / data.length
// Module scope: mark arrays are compared by identity, so an inline array would
// rebuild the chart on every render.
const average = [ruleY([mean], { stroke: 'var(--color-border)', strokeDasharray: '3 3' })]
const latest = [dot(data.slice(-1), { x: 'day', y: 'visitors', r: 3 })]
<Sparkline data={data} x="day" y="visitors" marksBefore={average} marks={latest} />The same rule applies to data: keep it stable across renders — defined at module scope, or memoized — and the chart rebuilds only when it actually changes.
Accessibility
ariaLabel is required — a chart is a figure, not decoration, and a sparkline carries no axis labels to fall back on. Describe the series and its window, not the shape: "Revenue over the last six months".
The chart surface is keyboard-focusable; arrow keys move between points and Home and End jump to the ends, so a sparkline stays reachable even with the tooltip off. When the sparkline sits beside the number it summarizes, keep that number in the DOM as text — it is what most assistive technology will read first.
If you color a sparkline by its direction, do not let color carry that meaning alone: print the delta next to it, as the stat card example does.
Examples
Default
Area
With Average
Trend Coloring
Signups+89%
Active users-25%
Stat Card
+14.5% vs. last month
API Reference
Sparkline
A tiny, chrome-free chart for stat cards and table cells: one series, no axes, no grid, no legend, and no tooltip unless you ask for one. Every interaction and host prop of `Chart` also applies — see `ChartBehaviorProps` and `ChartProps`.
| Prop | Type | Default | |
|---|---|---|---|
readonly unknown[] | — | ||
string | — | ||
string | — | ||
"area" | "line" | "line" | ||
ChartCurve | "natural" | ||
string | "var(--chart-1)" | ||
"gradient" | number | 0.2 | ||
number | 2.25 | ||
number | 40 | ||
boolean | false | ||
string | — | ||
string | — | ||
readonly ChartMark<unknown, any, any, any, any>[] | — | ||
readonly ChartMark<unknown, any, any, any, any>[] | — |
Interaction and animation props are shared by every chart family:
Interaction and animation props shared by every chart family component. They are flat scalars on purpose: the chart definition is memoized on a serialized key, and a nested option object would silently go stale.
| Prop | Type | Default | |
|---|---|---|---|
ChartFocus | "group-x" | ||
number | — | ||
ChartTooltipAnchor | "group-center" | ||
boolean | true | ||
false | — | ||
ChartAnimate | { duration: 240, respectReducedMotion: true } |
The host props — height, width, className, and the focus callbacks — are documented on the Chart page.
Last updated on 8/1/2026