# BreathingDot (/docs/charts/breathing-dot)

BreathingDot answers "how loaded is the system right now?" as an ambient signal. In the interactive entry the dot
**pulses** — slow and small when calm, fast and large when strained — so you feel the state without reading a number.
The static frame is a real chart with zero JavaScript: a core dot colored by band, and a ring whose distance from the
core is the level. This is a low-precision ambient read; for an exact figure reach for `Progress` or `Sparkline`.

```tsx
<BreathingDot value={0.42} title="Load" size={40} />
```

## Install

```tsx
import { BreathingDot } from "@microcharts/react/breathing-dot";

<BreathingDot value={0.42} title="Load" />
```

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


## Motion, and reduced motion

The pulse **is** the encoding — its rate and amplitude are snapped to three named bands (calm, elevated, strained), so
the motion is readable, not decorative. This is the deliberate exception to the no-looping-animation rule, allowed only
because the loop parameter (the rate) is the datum. The animation is gated on two things: it pauses when the dot scrolls
off-screen (a shared observer), and it never runs at all under `prefers-reduced-motion` — reduced-motion readers get
exactly the static frame, where the ring's distance from the core already carries the level. The band is announced
through a polite live region only when it changes, never on every pulse.

## When to use it

- **Good for** — an ambient "how strained is it right now" read, a live status dot in a header or KPI card, or per-node
  load in a dense table.
- **Avoid for** — an exact load figure (`Progress`, `Sparkline`), discrete events (`HeartbeatBlip`), or a trend over
  time (`Sparkline`).

## Sizing

One `size` prop, in viewBox units, sets the glyph box — it defaults to `16`, about the height of a line of body text.
There is no separate `width`/`height`: the dot is square, and the rendered box widens only by the gutter a `label`
reserves. `fontSize` follows `size` unless you set it.

## Variants

```tsx
<BreathingDot value={0.72} thresholds={[0.6, 0.85]} />
<BreathingDot value={0.42} label="value" />
```

## Edge cases

```tsx
<BreathingDot value={null} />
```

```tsx
<BreathingDot value={1.4} label="value" />
```

```tsx
<BreathingDot value={Number.NaN} />
```

```tsx
<BreathingDot value={0.723} label="value" locale="de-DE" />
```

`format`/`locale` reach the `label="value"` percent numeral — always a whole percent, so a locale mostly changes digit
shapes here, not grouping or decimals. The band words in the accessible summary ("calm" / "elevated" / "strained") stay
in `strings` (`BreathingDotStrings`), not the number formatter.


## Why this default

Three bands, because ambient motion supports about three discriminable states — more would be false precision. The band
color is always doubled: by the ring offset in the static frame and by the pulse rate in motion, so the state never
rests on color alone. When the value is unknown (`null` or `NaN`) the dot goes gray, drops its ring, and **never
pulses** — an unknown system must not look calm. The boundary with `HeartbeatBlip`: a continuous level is a
BreathingDot; discrete arriving events are a HeartbeatBlip.

## Accessibility

The accessible name is the level and its band word — **"Load 20% — calm."** — or **"Load unknown."** when the value is
missing. The band color is never the only signal (ring offset and pulse rate double it), motion is fully gated on
`prefers-reduced-motion`, and the live region announces band changes only.

This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Level 0–1 (clamped). null / NaN → unknown. |
| `thresholds` | `[number, number]` | calm / elevated / strained edges (default [0.5, 0.8]). |
| `label` | `"value" \| "none"` | Percent numeral beside the dot. |

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).
