Sparkline
Tiny inline SVG trend line for tables, stat cards, and dashboards.
Live demo
01Trend directions
Each line auto-scales to its own range β rising, falling, and volatile series read at a glance, no axes required.
Area fill and last-point dot
fill washes a soft area under the curve; last marks the final
reading with a ringed dot β useful for βwhere are we now?β emphasis.
In a stat table
The home turf for sparklines: a trend column beside each metric. Set width/height to fit the cell.
| Metric | Now | Last 7 days |
|---|---|---|
| Sessions | 48.2k | |
| Bounce rate | 32% | |
| Avg. order | Β£64 | |
| Refunds | Β£1.2k |
Edge cases
Empty and single-value series stay the right size and announce themselves sensibly to assistive tech rather than breaking the row.
Implementation
02<script lang="ts">
import Sparkline from '$lib/components/Sparkline.svelte';
const data = [12, 15, 13, 19, 22, 21, 27];
</script>
β
<div style="width: 90px; height: 24px;">
<Sparkline {data} stroke="#16a34a" width={90} height={24} fill last label="Sessions" />
</div>Sparkline maps a numeric series onto a tiny SVG box. It auto-scales to the data range β the smallest value sits at the bottom edge, the largest at the top β using t = (v - lo) / (hi - lo) and inverting for SVG's downward y-axis. A single point is pinned to the left edge and drawn as a dot; a flat series rests on the centre line. Optional area fill and a last-point dot are extra SVG nodes layered over the polyline. A computed aria-label summarises the trend (direction and percentage change) for screen readers.
Logic explainer
03What Does It Do? (Plain English)
A Sparkline is a tiny, word-sized chart that lives inline β in a table cell, beside a number on a stat card, or tucked into a dashboard tile. It strips a chart down to its essence: just the shape of the trend, no axes, no gridlines, no legend. You hand it an array of numbers and it draws a single line showing how they move from left to right.
Think of it as a heart-rate trace on a hospital monitor: at a glance you read the rhythm β climbing, falling, steady β without ever caring about the exact tick marks.
How It Works (Pseudo-Code)
inputs:
data = [ ...numbers ]
width, height
min, max (optional overrides)
derive clean:
keep only finite numbers from data
derive scale:
lo = min ?? Math.min(clean)
hi = max ?? Math.max(clean)
span = hi - lo
derive points:
for each value v at index i:
x = pad + (i / (count - 1)) * innerWidth
t = span == 0 ? 0.5 : (v - lo) / span // flat series β centre line
y = pad + (1 - t) * innerHeight // invert: SVG y grows downward
render:
if count >= 2 β <polyline points={points}>
if count == 1 β <circle> at the single point
if fill β <path> closing line down to baseline
if last β <circle> on final point
aria-label:
"Trend up/down/flat from {first} to {last}, +/-{pct}%"The Core Concept: Auto-Scaling To The Data Range
The job of a sparkline is to use every available pixel of its little box. It does this by mapping the data's own minimum and maximum onto the box's vertical extent, rather than anchoring to zero like a bar chart.
Given a value v, a domain [lo, hi], and an inner drawing height innerH:
t = (v - lo) / (hi - lo) // normalise to 0..1
y = pad + (1 - t) * innerH // 0 sits at the bottom, 1 at the topThe (1 - t) inversion matters because SVG's y-axis grows downward β without it, your highest revenue month would plot at the floor.
Two awkward inputs are handled explicitly. A single point would make i / (count - 1) divide by zero, so it is pinned to the left edge and drawn as a dot. A flat series has span === 0, which would divide by zero in t; instead t is forced to 0.5 so the line rests on the vertical centre rather than vanishing against the top edge.
Passing min and max opts out of auto-scaling. This is essential when you render a column of sparklines that must be visually comparable β pin them to a shared domain (e.g. min={0} max={100}) so a tall spike in one row genuinely means a bigger number than a flat line in another.
CSS Animation Strategy
The line draws itself in using the classic stroke-dasharray / stroke-dashoffset trick: the dash is set longer than the path, then the offset animates from 1000 to 0, revealing the stroke left-to-right over 0.8s. Only stroke-dashoffset animates, which the browser can composite without re-layout.
Under prefers-reduced-motion: reduce the animation is removed and stroke-dasharray is reset to none, so the full line is painted instantly with no movement.
Browser Support
Everything here is plain SVG 1.1 plus CSS custom properties β universally supported across evergreen browsers and back to IE-era SVG rendering. The one detail worth naming is vector-effect: non-scaling-stroke, which keeps the 1.5px line crisp even though preserveAspectRatio="none" stretches the viewBox non-uniformly to fill its container. It is supported in all modern browsers; where it is absent the stroke simply scales with the box (slightly thicker on wide cards), which degrades gracefully.
State Flow Diagram
Sparkline is a pure presentational component β its "state" is entirely derived from the data prop, so the diagram tracks which branch of the render renders.
data prop changes
β
βΌ
βββ filter finite βββ
β β
clean.length clean.length
== 0 == 1
β β
βΌ βΌ
[empty svg] [single dot]
β² β²
β β
βββββ clean.length >= 2 ββββββ
βΌ
[polyline drawn]
β β
fill=true last=true
β β
βΌ βΌ
[+ area path] [+ end dot]Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
data |
number[] |
[] |
The series to plot, left to right; non-finite entries are dropped. |
width |
number |
120 |
SVG viewBox width in user units. |
height |
number |
32 |
SVG viewBox height in user units. |
stroke |
string |
'currentColor' |
Line and dot colour; accepts any CSS colour. |
fill |
boolean |
false |
Draw a soft translucent area beneath the line. |
last |
boolean |
false |
Render a dot on the final data point. |
min |
number | undefined |
undefined |
Override the scale minimum to pin the baseline. |
max |
number | undefined |
undefined |
Override the scale maximum to pin the ceiling. |
label |
string |
'' |
Prefix for the computed aria-label (e.g. the metric name). |
Edge Cases
| Situation | Behaviour |
|---|---|
data is empty ([]) |
Renders an empty SVG of the correct size; aria-label reads "No data". |
data has exactly one value |
Plots a single dot at the left edge; aria-label reads "Single value N". |
| All values are equal (flat series) | Line sits on the vertical centre rather than the top edge. |
data contains NaN / Infinity |
Those entries are silently filtered out before scaling. |
First value is 0 |
Percentage change is omitted from the label (division by zero avoided). |
min/max set tighter than the data |
Values still plot; points beyond the domain extend past the box (overflow visible). |
prefers-reduced-motion: reduce |
The draw-in animation is suppressed; the line appears instantly. |
Dependencies
- Zero external dependencies β pure Svelte 5.
File Structure
src/lib/components/Sparkline.svelte # implementation (SVG + scoped CSS)
src/lib/components/Sparkline.md # this file (rendered inside ComponentPageShell)
src/lib/components/Sparkline.test.ts # vitest unit tests
src/routes/sparkline/+page.svelte # demo pageAPI
04| Prop | Type | Default | Description |
|---|---|---|---|
data | number[] | [] | The series to plot, left to right; non-finite entries are dropped. |
width | number | 120 | SVG viewBox width in user units. |
height | number | 32 | SVG viewBox height in user units. |
stroke | string | 'currentColor' | Line and dot colour; accepts any CSS colour. |
fill | boolean | false | Draw a soft translucent area beneath the line. |
last | boolean | false | Render a dot on the final data point. |
min | number | undefined | undefined | Override the scale minimum to pin the baseline across charts. |
max | number | undefined | undefined | Override the scale maximum to pin the ceiling across charts. |
label | string | '' | Prefix for the computed aria-label (e.g. the metric name). |