Skip to content
microcharts
ReferenceCoreDumbbell

Dumbbell

Dumbbell links a before dot and an after dot per row on one shared scale.

encodes
two dot positions + connecting span
precision
high
nodes
≤ 3 per row (rows ≤ 5)
collection
Core
Source

Dumbbell draws each row as a hollow dot at from, a filled dot at to, and a connector between them: a band that moved 62,000 → 84,000 reads without a legend. Direction is shape-coded, so it never relies on color alone, and the pair renders the same whichever way the values run. With positive the connector takes the valence token by direction. Drop positive for ranges such as min→max or a confidence span, which have no valence to color.

Dumbbell
interactive · 5.97 kB · static · 3.31 kB

Install

Import & usereact/dumbbell
import { Dumbbell } from "@microcharts/react/dumbbell";<Dumbbell data={[{ from: 62000, to: 84000 }]} title="Band move" />
Needs package + stylesheetSet up with AI

Try it

Live playground
onActive
idle
  1. hover · rove · click…

Rove rows with ↑/↓; ←/→ inspect the from/to ends of the active row.

valence
value labels
highlight Berlin
readout
animate
import { Dumbbell } from "@microcharts/react/dumbbell/interactive";// bands — real values under “Sample data” below<Dumbbell  data={bands}/>
Sample data
const bands = [  { label: "Paris", from: 52, to: 61 },  { label: "Berlin", from: 48, to: 68 },  { label: "Oslo", from: 66, to: 60 },  { label: "Rome", from: 44, to: 50 },];

When to use it

Use it for salary bands, before/after per table row, and ranges. When you have many categories and the crossings matter, use Slope; when the path between the two ends matters, use Sparkline.

Sizing

table cell
a range, not a change

Variants

valence
values

Edge cases

from === to — a single dot

When a row's from equals its to, the connector is dropped and one filled dot is drawn, rather than a hollow and a filled dot stacked on the same coordinate. The summary says "No change at 55." for a single row.

Four homes

In a sentence

Berlin's band moved from €48k to €68k after the review — up 42%.

In a table cell
Paris+9k
Berlin+20k
Oslo-6k
Rome+6k
In a KPI card
Largest band move
€48k → €68kBerlin, up 42%
In a tab header
OfficesLevels

Preview uses the public chart API only. When an interactive twin exists, it swaps in with the same props and no entrance motion - code samples stay on the static import. Placement recipes: Composition, Sizing.

Accessibility

A single row reads "From 40 to 60, up 50%.", a degenerate pair reads "No change at 55.", and a multi-row chart leads with the largest change ("2 rows. Largest change Paris, up 17%."). The interactive entry roves rows with ↑/↓ (←/→ do the same) and announces each row's own pair: "From 52 to 61, up 17%."

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, press Escape, or press outside the chart. On touch, a tap pins and a drag scrubs.

Props

PropTypeNotes
data*{ label?; from; to }[]Start/end pairs.
positive"up" | "down"Direction valence for CHANGES; drop it for ranges (no valence).
label"value" | "none"From/to values outside the dots (drop when the span is tight).
highlightnumber | stringAccent one row.
animatebooleaninteractiveOpt-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.

Related charts