# Slope (/docs/charts/slope)

Slope draws one line per category between two moments: East 40 to 47, West 55 to 41, and any crossing between them. Both
columns share one y-domain, since per-column normalization would fake convergence. Lines stay neutral until you declare
`positive`, because a rank change is not automatically good or bad.

```tsx
import { Slope } from "@microcharts/react/slope";

<Slope data={[
  { label: "East", from: 40, to: 47 },
  { label: "West", from: 55, to: 41 },
  { label: "South", from: 30, to: 33 },
]} title="Before vs after" />
```

## Install

```tsx
import { Slope } from "@microcharts/react/slope";

<Slope data={cohorts} title="Before vs after" />
```

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

## Try it

```tsx
import { Slope } from "@microcharts/react/slope/interactive";

<Slope
  data={cohorts}
/>
```

## When to use it

Use it for before/after experiments, rank shuffles, and two-moment comparisons, up to about 7 categories. A two-point
line says nothing about the path between the moments, so use Sparkline when that path is the point.

## Sizing

**KPI before/after**

```tsx
<Slope data={cohorts} label="both"
  width={140} height={96} />
```

**one vs the field**

```tsx
<Slope data={cohorts} highlight="West" />
```

## Variants

```tsx
const rows = [
  { label: "East", from: 40, to: 47 },
  { label: "West", from: 55, to: 41 },
  { label: "Mid", from: 20, to: 35 },
];

<Slope data={rows} label="both" />
<Slope data={rows} positive="up" highlight="West" />
```

End labels drop deterministically rather than by measurement: when the rows are denser than the label font (height ÷
count), and when the reserved label gutters would squeeze the two columns under ~35% of the width. The reclaimed room
goes back to the lines. A category name gets as many characters as the width affords, up to 14; under four it drops and
hands its gutter back, so a wide chart shows a name a narrow one cannot. Each surviving label seats on its own endpoint
and moves at most half a glyph pitch to clear the label above it. One that needs more than that drops, which keeps every
name readable against the line it belongs to. Endpoints that would collide inside a column are nudged half a unit apart.

## Edge cases

```tsx
// NaN for either side draws a dashed stub toward the missing end,
// announced "incomplete" — never interpolated
<Slope
  data={[
    { label: "East", from: 40, to: 47 },
    { label: "New", from: NaN, to: 41 },
  ]}
/>
```

```tsx
<Slope data={[{ label: "East", from: 40, to: 47 }]} />
```

```tsx
<Slope
  data={[
    { label: "East", from: 4000, to: 4700 },
    { label: "West", from: 5500, to: 4100 },
  ]}
  label="both"
  locale="de-DE"
/>
```

A row missing one side (`from` or `to` as `NaN`) draws a short dashed stub toward the end it does have rather than a
full line. There is no second point to connect to, so nothing is interpolated, and the row is announced as "incomplete".
With a `locale`, both column labels and the announced values follow that locale's own grouping.

## Four homes

**In a sentence**

```tsx
<p>
  West's renewal rate slid from best to worst region{" "}
  <span className="mc-inline">
    <Slope data={[{ label: "West", from: 55, to: 41 }]} width={40} height={18} summary={false} />
  </span>{" "}
  — down 25%.
</p>
```

**In a table cell**

```tsx
<td>
  <Slope data={[{ label: "East", from: 40, to: 47 }]} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">20% → 35%</span>
  <span className="unit">Mid region, up 75%</span>
  <Slope data={cohorts} positive="up" highlight="Mid" width={140} height={90} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Regions <Slope data={cohorts} width={64} height={20} />
</button>
```

## Accessibility

The accessible name counts directions and leads with the biggest mover: **"3 categories: 2 up, 1 down. Largest change
Mid, up 75%."** The interactive entry finds the nearest line under the pointer and roves categories ordered by their
after-value, announcing each slope (**"East: 40 to 47, up 18%."**).

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

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; from; to }[]` | Two aligned moments per category. |
| `label` | `"none" \| "value" \| "label" \| "both"` | End labels; dropped deterministically when rows collide. |
| `highlight` | `number \| string` | The one-vs-field editorial read. |
| `positive` | `"up" \| "down"` | Direction valence; unset = neutral ink. |
| `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).
