Skip to content
Dethink Components

ComponentsSparkline

Sparkline

A trend without the chart chrome. Put it beside a number, inside a table row, or at the foot of a KPI tile.

On this page

Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.

View registry files

Implemented with React and browser APIs, without an additional behavior or rendering library.

Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.

Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.

Import the component into your page or component file.

Usage
import { Sparkline } from "@dethink/components";

Three shapes for the same data, trends that sit inside table rows, and the full chart palette.

Line, area and bar

Line for continuous trends, a gradient area for volume, and bars for discrete periods with the latest emphasized.

Line
Area
Bar
Show sourceexamples/sparkline/variants.tsx
examples/sparkline/variants.tsx
"use client";

import { Sparkline, type SparklineVariant } from "@dethink/components";

const weeklyRevenue = [42, 48, 45, 53, 51, 58, 64, 61, 70, 74, 72, 81];

const variants: Array<{ variant: SparklineVariant; label: string }> = [
  { variant: "line", label: "Line" },
  { variant: "area", label: "Area" },
  { variant: "bar", label: "Bar" },
];

export function SparklineVariants() {
  return (
    <div className="grid w-full gap-6 sm:grid-cols-3">
      {variants.map(({ variant, label }) => (
        <figure key={variant} className="grid min-w-0 gap-3">
          <Sparkline
            data={weeklyRevenue}
            variant={variant}
            markers={variant === "bar" ? undefined : ["min", "max", "last"]}
            label={`Weekly revenue, ${label.toLowerCase()} sparkline`}
            formatValue={(value) => `$${value}K`}
            className="h-14"
          />
          <figcaption className="text-muted-foreground text-xs">
            {label}
          </figcaption>
        </figure>
      ))}
    </div>
  );
}

Inline in a table

Each metric keeps its own slot. A missing week stays a visible gap instead of an invented value.

MetricCurrentLast 10 weeks
Active workspaces12,408
API p95 latency182 ms
Weekly signups1,284
Open incidents3
Show sourceexamples/sparkline/metrics-table.tsx
examples/sparkline/metrics-table.tsx
"use client";

import {
  Sparkline,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
  type ChartColor,
} from "@dethink/components";

const rows: Array<{
  metric: string;
  value: string;
  color: ChartColor;
  trend: Array<number | null>;
}> = [
  {
    metric: "Active workspaces",
    value: "12,408",
    color: "chart-1",
    trend: [9.1, 9.4, 9.8, 10.1, 10.6, 10.9, 11.2, 11.8, 12.1, 12.4],
  },
  {
    metric: "API p95 latency",
    value: "182 ms",
    color: "chart-2",
    trend: [240, 232, 251, 220, 205, 212, 198, 190, 188, 182],
  },
  {
    metric: "Weekly signups",
    value: "1,284",
    color: "chart-3",
    trend: [860, 910, null, 1020, 980, 1110, 1150, 1210, 1190, 1284],
  },
  {
    metric: "Open incidents",
    value: "3",
    color: "chart-7",
    trend: [6, 4, 7, 5, 3, 4, 2, 5, 4, 3],
  },
];

export function SparklineMetricsTable() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Metric</TableHead>
          <TableHead className="text-end">Current</TableHead>
          <TableHead className="w-40">Last 10 weeks</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {rows.map((row) => (
          <TableRow key={row.metric}>
            <TableCell className="font-medium">{row.metric}</TableCell>
            <TableCell className="text-end tabular-nums">{row.value}</TableCell>
            <TableCell>
              <Sparkline
                data={row.trend}
                color={row.color}
                label={`${row.metric}, last 10 weeks`}
                className="h-7"
              />
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

Chart palette

Eight categorical slots, assigned in order. Series keep their slot when others are filtered out.

  • Bluechart-1
  • Orangechart-2
  • Aquachart-3
  • Yellowchart-4
  • Magentachart-5
  • Greenchart-6
  • Violetchart-7
  • Redchart-8
Show sourceexamples/sparkline/palette.tsx
examples/sparkline/palette.tsx
"use client";

import { Sparkline, type ChartTokenColor } from "@dethink/components";

const slots: Array<{ token: ChartTokenColor; hue: string }> = [
  { token: "chart-1", hue: "Blue" },
  { token: "chart-2", hue: "Orange" },
  { token: "chart-3", hue: "Aqua" },
  { token: "chart-4", hue: "Yellow" },
  { token: "chart-5", hue: "Magenta" },
  { token: "chart-6", hue: "Green" },
  { token: "chart-7", hue: "Violet" },
  { token: "chart-8", hue: "Red" },
];

// Deterministic, gently varied series so each slot reads as its own line.
function series(seed: number) {
  return Array.from({ length: 16 }, (_, i) =>
    Math.round(50 + 18 * Math.sin((i + seed * 1.7) / 2.4) + i * (seed % 3)),
  );
}

export function SparklinePalette() {
  return (
    <ul className="grid w-full gap-x-6 gap-y-5 sm:grid-cols-2">
      {slots.map(({ token, hue }, index) => (
        <li
          key={token}
          className="grid min-w-0 grid-cols-[7rem_1fr] items-center gap-4"
        >
          <span className="grid gap-0.5">
            <span className="flex items-center gap-2 text-sm font-medium">
              <span
                aria-hidden="true"
                className="size-2.5 rounded-full"
                style={{ background: `var(--dt-color-${token})` }}
              />
              {hue}
            </span>
            <code className="text-muted-foreground font-mono text-xs">
              {token}
            </code>
          </span>
          <Sparkline
            data={series(index + 1)}
            variant="area"
            color={token}
            label={`${hue} series sample`}
            className="h-10"
          />
        </li>
      ))}
    </ul>
  );
}
Sparkline props
PropWhat it doesDefault
dataArray<number | null | undefined>Values in order. Missing values leave a gap in the line.Not set
variant"line" | "area" | "bar"Line, line with a gradient wash, or rounded bars."line"
color"chart-1" … "chart-8" | stringPalette slot resolved to --dt-color-chart-N, or any CSS color."chart-1"
curve"monotone" | "linear" | "step"Monotone smoothing never overshoots the data."monotone"
markersboolean | Array<"first" | "last" | "min" | "max">Point markers for line and area. true shows min, max and last.["last"]
emphasis"last" | "none"Bar variant: mute every bar except the latest period."last"
domain[number, number]Fixed value range, for comparing sparklines on a shared scale. Bars always include zero.Data extent
label / formatValuestring / (value: number) => stringStart of the accessible summary and how its values are written."Trend" / compact
decorativebooleanHide from assistive technology when nearby text already carries the data.false
animatebooleanDraw-in on mount. Always off under reduced motion.true

Every Dethink chart reads its colors from --dt-color-chart-1 to --dt-color-chart-8, plus -grid, -axis and -crosshair for chart chrome. Each slot has separate light and dark values, and Tailwind exposes them as bg-chart-3, stroke-chart-3 and so on. The default order is validated for color-vision deficiency on both surfaces. Aqua, yellow and magenta sit below 3:1 contrast on white, so pair them with labels or a table. If you replace the palette, re-check it for adjacent-pair separation before shipping.

globals.css
/* Override any slot, per theme, after the Dethink base styles. */
:root {
  --dt-color-chart-1-light: oklch(0.55 0.2 265);
  --dt-color-chart-1-dark: oklch(0.68 0.17 265);
}

/* Or scope a palette to one dashboard. */
.finance-dashboard {
  --dt-color-chart-1: var(--brand-ink);
}

A Sparkline is an image with a generated summary: the count, first and last values, low and high. Set label to name the metric and period, and formatValue to match how the value appears elsewhere. Inside a tile or row whose text already states the numbers, set decorative so screen readers do not hear them twice. The draw-in animation is skipped when reduced motion is requested.