Mischief

135 / Charts

Sparkline

A trend line small enough for a table cell, with the change since the first value and a readout you can move along it.

Active users

2,418

+22.1%
p95 latency in milliseconds, last 14 days. Lower is better.
RouteTodayTrend
GET /search141 ms
-22.5%
GET /search p95 latency
PointValue
Oct 20182 ms
Oct 21176 ms
Oct 22190 ms
Oct 23201 ms
Oct 24188 ms
Oct 25170 ms
Oct 26164 ms
Oct 27171 ms
Oct 28168 ms
Oct 29159 ms
Oct 30162 ms
Oct 31150 ms
Nov 1148 ms
Nov 2141 ms
POST /checkout398 ms
+27.6%
POST /checkout p95 latency
PointValue
Oct 20312 ms
Oct 21305 ms
Oct 22298 ms
Oct 23320 ms
Oct 24344 ms
Oct 25351 ms
Oct 26362 ms
Oct 27No data
Oct 28371 ms
Oct 29380 ms
Oct 30366 ms
Oct 31390 ms
Nov 1402 ms
Nov 2398 ms
GET /orders/:id90 ms
+2.3%
GET /orders/:id p95 latency
PointValue
Oct 2088 ms
Oct 2190 ms
Oct 2287 ms
Oct 2391 ms
Oct 2489 ms
Oct 2588 ms
Oct 2690 ms
Oct 2792 ms
Oct 2889 ms
Oct 2988 ms
Oct 3090 ms
Oct 3191 ms
Nov 189 ms
Nov 290 ms

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/sparkline
import { Sparkline } from "mischief-ui/sparkline"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/sparkline/sparkline.tsx
"use client" import * as React from "react" import { cn } from "@/lib/utils" export type SparklineProps = Omit<  React.HTMLAttributes<HTMLDivElement>,  "children"> & {  /** One value per step, oldest first. null is a gap in the line. */  data: readonly (number | null)[]  /** What the line measures. Names the chart for assistive technology. */  label: string

Usage

export function SignupsCell({ days }: { days: Day[] }) {
  return (
    <Sparkline
      data={days.map((day) => day.signups)}
      labels={days.map((day) => day.label)}
      label="Signups, last 14 days"
    />
  )
}

Which change is good news

The badge compares the latest value with the first one and shows the difference as a signed percentage beside an arrow, so the direction is never carried by colour alone. A move in the wrong direction takes the destructive colour. For latency, error counts, and anything else where falling is the good outcome, set goodDirection to down.

<Sparkline
  data={p95}
  goodDirection="down"
  format={{ style: "unit", unit: "millisecond" }}
  label="Checkout p95 latency"
/>

When the first value is zero there is no meaningful percentage, so the badge is left out. showChange={false} removes it everywhere.

Gaps, flat lines, and negatives

A null in data is a missing day. The line breaks there instead of drawing a slope through a value nobody recorded, and the readout skips it. A flat series draws through the middle, a single value draws only its dot, and a series that crosses zero gets a hairline at zero.

Reading a point

Hovering moves a readout to the nearest point. With the keyboard the chart is one tab stop: the arrow keys step through the points, Home and End jump to the first and the latest. In a long table where every row has a sparkline, scrub={false} takes them out of the tab order.

API

data(number | null)[]One value per step, oldest first. null is a gap.
labelstringWhat the line measures. Starts the chart's accessible name.
labelsstring[]A name for each step, such as a date, for the readout and the table.
areabooleanFills the space under the line with a light wash.
showChangebooleanShows the change from the first value. Defaults to true.
goodDirection"up" | "down"Which way is good news. Defaults to up.
scrubbooleanLets a pointer or the arrow keys read any point. Defaults to true.
tablebooleanAdds a visually hidden table of every value.
formatIntl.NumberFormatOptionsHow values are written in the readout, the summary, and the table.
localestringThe locale for every number.
colorstringAny CSS colour. Defaults to var(--chart-1), then var(--primary).
heightnumberHeight of the plot in pixels. Defaults to 32. Width follows the element.
data-state, data-trend"idle" | "scrubbing", "up" | "down" | "flat"Written to the root for styling.
classNamestringClasses for size and placement. The default width is w-36.
...divPropsHTMLAttributesNative div attributes.

Accessibility

The line is an image whose name is written from the data: the low, the high, the latest value, the change since the first value, and how many points are missing. A slider sits over it as a single tab stop, and its value text names the point being read, such as Tue: 1,204, so the readout a pointer sees is also what a screen reader hears. The change badge pairs its colour with an arrow and a sign. table adds a hidden table of every value for anyone who wants the numbers rather than the shape.