Sparkline
A chart small enough to read as a word: no axes, no legend, no title — just the shape of a series, next to the number it belongs to.
Requests are up over the last twelve weeks while latency has come down .
import { Sparkline } from '@ontoui/react';
<Sparkline.Root data={[12, 18, 15, 26, 24, 33, 30, 41]}> <Sparkline.Line /> <Sparkline.Spot /></Sparkline.Root>;Anatomy
| Part | Description |
|---|---|
Sparkline.Root | <svg role="img"> — the box, the scale, and the accessible name. |
Sparkline.Line | <path> — the series as a stroked line. |
Sparkline.Area | <path> — the fill between the line and the baseline. |
Sparkline.Bars | <g> of <rect> — the series as bars standing on the baseline. |
Sparkline.Spot | <circle> — a dot on one point: the last reading, the peak, the low. |
Sparkline.Baseline | <line> — a rule at zero, a target, or an average. |
SVG paints in document order, so the parts are stacked in the order you write them: the area before the line it sits under, the baseline before the marks that cross it.
<Sparkline.Root data={data}> <Sparkline.Baseline /> <Sparkline.Area /> <Sparkline.Line /> <Sparkline.Spot /></Sparkline.Root>A sparkline is a trend, not a reading. It says up, and steeply toward the end — never 47.2. Put the number next to it in text; the chart carries the shape.
Data
data is a plain array of numbers, oldest first and evenly spaced. There is no x axis and no
notion of a date, so a gappy series has to arrive with its holes already in it: null — and
any value that is not a finite number — keeps its place on the axis and is not drawn.
<Sparkline.Root data={[12, 18, null, null, 24, 33, 30, null, 38, 47]}> <Sparkline.Line /> <Sparkline.Spot /></Sparkline.Root>The line breaks at a gap rather than drawing across it, and a reading left alone between two
gaps is drawn as a dot — it is still a reading. Sparkline.Spot skips gaps too, so a series
that ends in one still marks the last value there was.
Marks
<Sparkline.Root data={data}> <Sparkline.Line /></Sparkline.Root>
<Sparkline.Root data={data}> <Sparkline.Area /> <Sparkline.Line /> <Sparkline.Spot /></Sparkline.Root>
<Sparkline.Root data={data}> <Sparkline.Bars /></Sparkline.Root>Use a line for something continuous that is read as a shape — a rate, a price, a latency. Use
bars for counts read one at a time — deploys per day, errors per hour. Sparkline.Bars takes
its width from a gap that is given up before the bar is, so a hundred points still render as
hairlines rather than vanishing.
Sparkline.Spot is positioned from the line’s points, so pair it with Sparkline.Line rather
than with bars. It marks last by default, and takes first, min, max, or an index.
Scale
The range is taken from the data: the smallest value sits at the bottom of the box and the
largest at the top, so the shape fills the space it has. That makes two sparklines comparable
only when they are given the same bounds — pass min and max when a row of them has to be
read against each other, or when a flat week should look flat rather than dramatic.
<Sparkline.Root data={data} min={0} max={100}>baseline is what bars stand on and areas fill down to. It defaults to 0 when the range
spans it, and to the bottom of the range otherwise — so a series that crosses zero grows in
both directions without any configuration.
<Sparkline.Root data={[-4, -2, 1, 3, -1, 5, 6, 2, -3, 4, 7, 9]} width={160} height={40}> <Sparkline.Bars /> <Sparkline.Baseline /></Sparkline.Root>Sparkline.Baseline takes a value of its own for a target or an average, and stays inside
the box even when that value is outside the range.
Size
width and height are pixels, and they default to 80 × 20 — the size of a few words of
text. The geometry is drawn from them, so they are props rather than stylesheet overrides.
<Sparkline.Root data={data} width={148} height={36}>padding keeps room inside the box so a stroke or a dot at the very top of the range is not
clipped by the edge. Raise it along with r if you enlarge the spot.
In a metric card, size the chart to the card and hide it from screen readers — the value and the trend are already written above it:
<div className="card"> <span className="card-label">Requests / week</span> <span className="card-value">52k</span> <Sparkline.Root data={requests} width={148} height={36} aria-hidden> <Sparkline.Area /> <Sparkline.Line /> <Sparkline.Spot /> </Sparkline.Root></div>In a data grid
A sparkline is at its most useful in a column: one shape per row, all read down the page at
once. Give every cell the same width and height so the column scans as a single chart.
| Service | Errors | Last 8h |
|---|---|---|
| checkout | 0.4% | |
| search | 0.1% | |
| billing | 1.2% |
const columns: ColumnDef<Service, unknown>[] = [ { accessorKey: 'name', header: 'Service' }, { accessorKey: 'errors', header: 'Errors' }, { accessorKey: 'trend', header: 'Last 8h', cell: ({ row }) => ( <Sparkline.Root data={row.original.trend} width={72} height={18}> <Sparkline.Line /> <Sparkline.Spot /> </Sparkline.Root> ), },];Rows are scaled independently unless you pass min and max, so a column of sparklines
compares shapes, not sizes. When the column is meant to compare magnitudes, bound every row to
the same range.
Color
<Sparkline.Root data={data} color="danger">Color is decorative — the accessible name reads the same at any tint. Keep the meaning in the
text beside the chart: a red spark next to 1.2% errors, never a red spark on its own.
Styling
Every part is an ordinary SVG element, so its stroke, fill and width are ordinary CSS. Pass a
className to the part you want to change:
<Sparkline.Root data={data}> <Sparkline.Line className="thick" /></Sparkline.Root>.thick { stroke-width: 2.5;}The root publishes its tint as --sparkline-color, which every part draws from, so one
declaration restyles the whole series:
.brand { --sparkline-color: #7c3aed;}Accessibility
- The chart is one image.
Sparkline.Rootisrole="img"with a name worded from the series — “Sparkline: 12 points, from 18 to 46, trending up (low 12, high 51)”. The shapes inside it are not announced separately. - Say what the series is. The generated name describes the numbers, not the subject. Pass
getAriaLabelto name it:(s) => `Weekly requests, ${s.direction}, now ${s.format(s.last!)}`. - Hide it when the numbers are already written. In a metric card the value and the delta
are text on the same card, so
aria-hiddenon the chart avoids reading them twice. formatandlocalereach the announced numbers. A chart of currency or milliseconds should say so out loud, the same way the label beside it does.- Forced colors are handled. The marks fall back to system ink, so the chart does not go blank in a high-contrast theme.
API Reference
Sparkline.Root
| Prop | Type | Default |
|---|---|---|
children? | ReactNode | — |
className? | string | — |
style? | CSSProperties | — |
data | readonly (number | null | undefined)[] | — |
width? | number | 80 |
height? | number | 20 |
padding? | number | 3 |
min? | number | the smallest value in `data` |
max? | number | the largest value in `data` |
baseline? | number | `0` when the range spans it, and the bottom of the range otherwise |
color? | "default" | "success" | "warning" | "danger" | 'default' |
format? | NumberFormatOptions | — |
locale? | LocalesArgument | the runtime locale |
getAriaLabel? | (summary: SparklineSummary) => string | — |
ref? | Ref<SVGSVGElement> | — |
Sparkline.Line
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
ref? | Ref<SVGSVGElement> | — |
Sparkline.Area
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
ref? | Ref<SVGSVGElement> | — |
Sparkline.Bars
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
gap? | number | 2 |
radius? | number | 1 |
ref? | Ref<SVGSVGElement> | — |
Sparkline.Spot
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
point? | SparklineSpotPoint | 'last' |
r? | number | 2 |
ref? | Ref<SVGSVGElement> | — |
Sparkline.Baseline
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
value? | number | the root's `baseline` |
ref? | Ref<SVGSVGElement> | — |