PercentileLadder
PercentileLadder puts the chosen percentiles on one zero-anchored track, drawing the tail strongest.
- encodes
- tick position on a zero-anchored strip
- precision
- high — point estimates per percentile
- nodes
- ≤ 8
A service answers at 124.5 ms for half its requests and 1,661.13 ms for the slowest 1%. PercentileLadder puts both ticks
on one zero-anchored track, where the distance between them is the reading. Graduated height and accent make the
tail read strongest, because the tail is usually the SLO question. The origin is never cropped: cropping it would
distort every gap. A log transform for long tails is never silent — scale="log" applies only when every sample is
strictly positive, and an in-chart log tag renders whenever it does.
Install
import { PercentileLadder } from "@microcharts/react/percentile-ladder";<PercentileLadder data={Array.from({ length: 200 }, (_, i) => i < 130 ? 90 + (i % 50) : i < 180 ? 150 + ((i * 7) % 320) : i < 196 ? 480 + ((i * 11) % 900) : 1500 + ((i * 13) % 800), )} format={{ style: "unit", unit: "millisecond" }} title="Request latency"/>Try it
When to use it
Use it for latency SLOs in a sentence, the tail per endpoint in tables, and payment-size spread. For the odds of an outcome use QuantileDots; for the full distribution shape use MicroBox.
Sizing
Variants
Edge cases
When every sample is identical the ladder collapses to a single tick instead of stacking overlapping ones. A single
value at or below zero makes scale="log" fall back to linear, and no log tag renders, because the transform was not
applied. Below 56 px wide the tick labels drop out rather than overlap.
Four homes
Accessibility
The accessible name states every marked percentile and the tail's multiple of the median — "p50 124.5, p90 488.1, p99 1,661.13 — the slowest 1% take 13.3× the median." The interactive entry roves ticks with ←/→ (Home/End jump to the ends), announcing each tick's value and its multiple of the median ("p99: 1,661.13 — 13.3× the median.").
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | number[] | Raw sample; quantiles are derived. |
| ps | number[] | Percentiles to mark (default [50, 90, 99], 2–4). |
| scale | "linear" | "log" | Log for long tails (falls back on any value ≤ 0; renders a log tag). |
| label | "ps" | "values" | "both" | "none" | What the tick labels state. |
| marks | "tick" | "dot" | Tick marks (default) or dot marks — dots read calmer over dense text. |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.