# Dumbbell (/docs/charts/dumbbell)

Dumbbell answers "where did each row start and end?". The hollow dot is _before_, the filled dot is _after_ — direction
is shape-coded, so it never relies on color alone. With `positive` the connector takes the valence token by direction.

```tsx
<Dumbbell data={[{ from: 62000, to: 84000 }]} title="Band move" style={{ width: 200, height: 28 }} />
```

## Install

```tsx
import { Dumbbell } from "@microcharts/react/dumbbell";

<Dumbbell data={[{ from: 62000, to: 84000 }]} title="Band move" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.


## When to use it

- **Good for** — salary bands, before/after per table row, ranges.
- **Avoid for** — many categories where crossings matter (Slope) or the path between (Sparkline).


## Variants

```tsx
<Dumbbell
  data={[
    { label: "Paris", from: 52, to: 61 },
    { label: "Oslo", from: 66, to: 60 },
  ]}
  positive="up"
/>
```

```tsx
<Dumbbell data={[{ from: 40, to: 60 }]} label="value" domain={[0, 100]} />
```

## Edge cases

```tsx
// from === to → a single dot, no connector
<Dumbbell data={[{ from: 55, to: 55 }]} />
```

When a row's `from` equals its `to`, the connector is dropped and only one filled dot is drawn — never a
hollow-and-filled pair sitting on top of each other pretending to be two points. The summary says **"No change at 55."**
for a single row.


## Why this default

Hollow → filled reads as before → after without a legend, and renders identically whichever way the data runs —
direction comes from the values, not the array order. For _ranges_ (min→max, confidence spans) the docs require dropping
`positive`: a range has no valence, and coloring it green or red would invent one.

## Accessibility

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

The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 | Description |
| --- | --- | --- |
| `data` (required) | `{ 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). |
| `highlight` | `number \| string` | Accent one row. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
