Siteplane UI

Sparkline

Show the trend of one or several measures in a small chart without axes.

Markdown

Visitors

12,840+18.2%

Visitors per day, last 14 days
"use client";

import { Sparkline } from "@/siteplane/components/sparkline";

// Visitors per day over the last 14 days, from a Thursday to a Wednesday.
const visitors = [
  960, 880, 390, 350, 1010, 1090, 1060, 1180, 1070, 430, 400, 1250, 1350, 1420,
];
const total = visitors.reduce((sum, count) => sum + count, 0);

export default function Example() {
  return (
    <div className="flex w-full max-w-sm items-end justify-between gap-6">
      <div className="grid gap-1">
        <p className="text-sm font-medium text-muted-foreground">Visitors</p>
        <p className="flex items-baseline gap-2">
          <span className="text-3xl font-semibold tabular-nums">
            {total.toLocaleString("en-US")}
          </span>
          <span className="text-sm font-medium text-success-foreground tabular-nums">
            +18.2%
          </span>
        </p>
      </div>
      <Sparkline
        className="w-40 min-w-0"
        color="var(--chart-brand)"
        data={visitors}
        height={56}
        label="Visitors per day, last 14 days"
      />
    </div>
  );
}

A sparkline shows at a glance how a measure has moved. It has no axes, labels or tooltip, so it shows the shape of the data and nothing more. Place it beside the figure it belongs to, in a table row, a card or a line of text, and name it with label.

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/sparkline

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/toggle-group

Usage

import { Sparkline } from "@/siteplane/components/sparkline";

<Sparkline label="Sign-ups per day" data={[12, 18, 14, 25, 31, 22, 38]} />;

Variants

variant sets how the data is drawn. line, the default, is the stroke alone. area adds a fill under the line that fades out toward the baseline, and fill keeps that fill without the line.

line

Revenue per day, last 14 days

area

Revenue per day, last 14 days

fill

Revenue per day, last 14 days
"use client";

import { Sparkline } from "@/siteplane/components/sparkline";

const revenue = [
  420, 610, 530, 780, 920, 690, 1040, 1150, 880, 1260, 1390, 1080, 1520, 1650,
];

const variants = ["line", "area", "fill"] as const;

export default function Example() {
  return (
    <div className="flex flex-wrap justify-center gap-x-12 gap-y-8">
      {variants.map((variant) => (
        <div className="grid w-36 gap-3" key={variant}>
          <p className="text-xs text-muted-foreground">{variant}</p>
          <Sparkline
            color="var(--chart-brand)"
            data={revenue}
            height={48}
            label="Revenue per day, last 14 days"
            variant={variant}
          />
        </div>
      ))}
    </div>
  );
}

Several series

Pass a list of series instead of one list of numbers. The series share one scale and one color, so they stand apart by their shape: here desktop visitors drop at the weekend while mobile visitors rise. With area each series keeps its line; fill reads best when they overlap.

area

Desktop and mobile visitors per day, last 14 days

fill

Desktop and mobile visitors per day, last 14 days
"use client";

import { Sparkline } from "@/siteplane/components/sparkline";

// Visitors per day over the last 14 days, from a Thursday to a Wednesday:
// desktop visitors drop at the weekend, mobile visitors rise.
const desktop = [
  880, 790, 300, 260, 920, 990, 960, 1010, 900, 340, 310, 1040, 1110, 1080,
];
const mobile = [
  360, 420, 720, 780, 340, 310, 380, 400, 450, 810, 860, 370, 350, 410,
];

const variants = ["area", "fill"] as const;

export default function Example() {
  return (
    <div className="flex flex-wrap justify-center gap-x-12 gap-y-8">
      {variants.map((variant) => (
        <div className="grid w-44 gap-3" key={variant}>
          <p className="text-xs text-muted-foreground">{variant}</p>
          <Sparkline
            color="var(--chart-brand)"
            data={[desktop, mobile]}
            height={56}
            label="Desktop and mobile visitors per day, last 14 days"
            variant={variant}
          />
        </div>
      ))}
    </div>
  );
}

Scale

By default the scale runs from 0 to the highest value, so the height of the line shows the size of the values, and a measure that moves by a few percent draws a nearly flat line. Set includeZero={false} to span the scale from the lowest value to the highest. A small change then shows its shape, but the line no longer shows its size, so write the figure beside it, as a metric card does.

From 0

Revenue per day, last 14 days

Lowest to highest

Revenue per day, last 14 days
"use client";

import { Sparkline } from "@/siteplane/components/sparkline";

// Revenue per day over the last 14 days, within a few percent of $8,750.
const revenue = [
  8420, 8510, 8380, 8640, 8710, 8590, 8820, 8760, 8690, 8910, 8980, 8850, 9060,
  9140,
];

const scales = [
  { includeZero: true, name: "From 0" },
  { includeZero: false, name: "Lowest to highest" },
];

export default function Example() {
  return (
    <div className="flex flex-wrap justify-center gap-x-12 gap-y-8">
      {scales.map((scale) => (
        <div className="grid w-40 gap-3" key={scale.name}>
          <p className="text-xs text-muted-foreground">{scale.name}</p>
          <Sparkline
            color="var(--chart-brand)"
            data={revenue}
            height={48}
            includeZero={scale.includeZero}
            label="Revenue per day, last 14 days"
            variant="area"
          />
        </div>
      ))}
    </div>
  );
}

Color

Without color, the sparkline is drawn in the text color around it. Give the parent of a rise text-success-foreground and the parent of a fall text-destructive-foreground, and the line takes the same color as the change beside it. Set color to a CSS color or token to give the sparkline a color of its own, such as var(--chart-brand), the brand color the other charts start from, in the other examples.

  • Sign-ups
    Sign-ups per day, last 14 days+83.1%
  • Orders
    Orders per day, last 14 days−41.2%
"use client";

import { Sparkline } from "@/siteplane/components/sparkline";

// Per day over the last 14 days; the change compares the last 7 days with the
// 7 days before.
const metrics = [
  {
    change: "+83.1%",
    data: [12, 15, 11, 18, 21, 17, 24, 27, 22, 30, 33, 28, 36, 40],
    label: "Sign-ups",
    tone: "text-success-foreground",
  },
  {
    change: "−41.2%",
    data: [41, 37, 39, 33, 35, 29, 31, 26, 27, 22, 23, 18, 16, 12],
    label: "Orders",
    tone: "text-destructive-foreground",
  },
];

export default function Example() {
  return (
    <ul className="grid w-full max-w-md gap-4">
      {metrics.map((metric) => (
        <li className="flex items-center gap-4" key={metric.label}>
          <span className="flex-1 text-sm font-medium">{metric.label}</span>
          <div className={`flex items-center gap-3 ${metric.tone}`}>
            <Sparkline
              className="w-28"
              data={metric.data}
              label={`${metric.label} per day, last 14 days`}
              variant="area"
            />
            <span className="w-16 text-end text-sm tabular-nums">
              {metric.change}
            </span>
          </div>
        </li>
      ))}
    </ul>
  );
}

Changing data

When data changes, the curve morphs into the new one, whatever its length, so a switch from 30 to 90 days glides instead of jumping. A series of one point or none, or a change in the number of series, shows at once.

Sign-ups per day, last 30 days
"use client";

import { useState } from "react";
import { Sparkline } from "@/siteplane/components/sparkline";
import {
  ToggleGroup,
  ToggleGroupItem,
} from "@/siteplane/components/toggle-group";

// Sign-ups per day over the last 90 days: growing, with fewer at the weekend.
// A shorter period is the end of it.
const signups = Array.from({ length: 90 }, (_, day) => {
  const weekday = (day + 4) % 7; // 0 is a Monday; the last day is a Wednesday
  const level = 12 + day / 5;
  return Math.round(
    level * (weekday >= 5 ? 0.4 : 1 + 0.18 * Math.sin(day * 2.3)),
  );
});

const periods = ["7", "30", "90"];

export default function Example() {
  const [days, setDays] = useState("30");
  return (
    <div className="grid w-full max-w-md gap-4">
      <ToggleGroup
        aria-label="Period"
        onValueChange={([next]) => {
          if (next) setDays(next);
        }}
        size="sm"
        value={[days]}
        variant="outline"
      >
        {periods.map((period) => (
          <ToggleGroupItem key={period} value={period}>
            {period} days
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <Sparkline
        color="var(--chart-brand)"
        data={signups.slice(-Number(days))}
        height={64}
        label={`Sign-ups per day, last ${days} days`}
        variant="area"
      />
    </div>
  );
}

API reference

Component properties
PropTypeDefaultDescription
Sparkline.datareadonly number[] | readonly (readonly number[])[]requiredOne series of numbers, or a list of series that share one scale. A value that is not a finite number is left out and its neighbors are joined.
Sparkline.labelstringrequiredAccessible name of the sparkline; it is also its native title.
Sparkline.variantline | area | filllineline draws the stroke alone; area adds a fill under it that fades out toward the baseline; fill draws that fill without the stroke.
Sparkline.colorstringcurrentColorA CSS color or token for the marks. Without it they take the text color around the sparkline, so a parent sets it.
Sparkline.heightnumber32Height in pixels. The width follows the room the sparkline is given.
Sparkline.includeZerobooleantrueTakes 0 into the scale, so the height of the line shows the size of the values. false spans the scale from the lowest value to the highest, so a small change still shows its shape; a series that is the same all along then sits in the middle.
Sparkline.classNamestringunsetAdditional classes. A width class such as w-24 replaces the full width.

SparklineProps and SparklineVariant export these contracts. The sparkline renders one svg element with role="img". Other SVG attributes, such as an id or data-* attributes, pass through to it; it accepts no children or ref.

Read the installed source.

Usage notes

Use a sparkline to show how a measure has moved beside its value. It shows the shape of the data only, so when readers need exact values, dates or several measures on a shared axis, use a Line Chart or a Table instead.

The sparkline fills the width it is given and is 32 pixels high by default. In a table cell or beside text, give it a width such as w-24, and set height in pixels. The stroke stays 2 pixels wide at any size.

All series share one scale. It includes 0 unless includeZero is false, so by default a series that varies little around a large value looks flat. A value that is not a finite number is left out and its neighbors are joined, so no gap is drawn. The line is a smooth curve through every point, and a series with only one value is drawn as a dot. A fill reaches from the line to the 0 line, so a series below 0 hangs down from it and one that crosses it fills toward it from both sides. When 0 lies outside the scale, as with includeZero={false}, the fill reaches the edge of the box instead.

The sparkline is an image named by label. It is not a focus stop and shows no values on hover, focus or tap. Screen readers announce the label and not the values, so say in the label what the sparkline shows and over which period, and write the figure beside it as text.

The sparkline renders its complete SVG on the server. It is drawn in its own units and stretched to its box, so it never measures its container. New data and a change of color animate with the Motion tokens (--motion-duration-value and --motion-ease-standard), and instantly under reduced motion: the system setting, html with data-motion-reduce="true" or SiteplaneMotionProvider preset="reduced".