Sparkline

Tiny inline SVG trend line for tables, stat cards, and dashboards.

Live demo

01

Trend directions

Each line auto-scales to its own range β€” rising, falling, and volatile series read at a glance, no axes required.

Rising
Falling
Volatile
Flat

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.

Line only
fill
fill + last

In a stat table

The home turf for sparklines: a trend column beside each metric. Set width/height to fit the cell.

MetricNowLast 7 days
Sessions48.2k
Bounce rate32%
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.

Empty
Single value
Pinned min/max (0–20)

Implementation

02
Sparkline.svelte
<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

03

What 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 top

The (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 page

API

04
PropTypeDefaultDescription
datanumber[][]The series to plot, left to right; non-finite entries are dropped.
widthnumber120SVG viewBox width in user units.
heightnumber32SVG viewBox height in user units.
strokestring'currentColor'Line and dot colour; accepts any CSS colour.
fillbooleanfalseDraw a soft translucent area beneath the line.
lastbooleanfalseRender a dot on the final data point.
minnumber | undefinedundefinedOverride the scale minimum to pin the baseline across charts.
maxnumber | undefinedundefinedOverride the scale maximum to pin the ceiling across charts.
labelstring''Prefix for the computed aria-label (e.g. the metric name).