# TimeInRange (/docs/charts/time-in-range)

TimeInRange answers "how much of the period was the metric inside its acceptable corridor — and which side did it miss
on?". Zones sit in a fixed semantic order — below, in, above — so the strip is read by position first and color second.
The in-range percent is the one number that matters.

```tsx
<TimeInRange data={{ below: 9, in: 72, above: 19 }} title="Time in range" width={240} height={22} />
```

## Install

```tsx
import { TimeInRange } from "@microcharts/react/time-in-range";

<TimeInRange data={{ below: 9, in: 72, above: 19 }} title="Time in range" />
```

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** — SLO / uptime corridors, glucose-style time-in-range, thermal or budget bands.
- **Avoid for** — ranking parts (SegmentedBar) or a single ratio (Progress).


## Variants

```tsx
<TimeInRange
  data={{ severeBelow: 2, below: 7, in: 72, above: 15, severeAbove: 4 }}
  orientation="vertical"
  label="all"
/>
```

```tsx
<TimeInRange data={{ severeBelow: 3, below: 8, in: 64, above: 18, severeAbove: 7 }} />
```

## Edge cases

```tsx
// all-zero data renders an empty strip, never a misleading full bar
<TimeInRange data={{ below: 0, in: 0, above: 0 }} title="No readings yet" />
```

```tsx
// label="all" only shows a percent where the zone is wide enough to seat it —
// the tiny below/above slivers here drop their text instead of overlapping
<TimeInRange data={{ below: 3, in: 90, above: 7 }} label="all" width={60} height={10} />
```


## Why this default

Zone order is semantic and immutable — it is never sorted by magnitude, because the position of a miss (too low vs too
high) is half the reading. Severity tiers are drawn with the same hue at greater ink weight, so the strip never relies
on color alone. Percent labels round with largest-remainder rounding so they always sum to 100, and the summary reads
the same integers as the label.

## Accessibility

The accessible name leads with the headline and then the misses — **"72% in range, 7% below, 15% above, 2% severe low,
4% severe high."** The interactive entry roves the zones; each announces its share (**"in range: 72%."**).

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) | `TimeInRangeDatum` | Counts or fractions; normalized to 1. |
| `orientation` | `"horizontal" \| "vertical"` | Vertical suits clinical columns and KPI cards. |
| `label` | `"in" \| "all" \| "none"` | The in-range headline, a full audit, or clean. |
| `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).
