BreathingDot
Ambient system load as a colored dot with an offset ring, pulsing at a rate set by its band.
- encodes
- pulse rate + amplitude (static: ring offset) by level
- precision
- low
- nodes
- 3
BreathingDot takes a single level between 0 and 1 and shows how strained a system is right now, as an ambient signal you
can leave running in a corner of the interface. A core dot colored by band, and a ring whose distance from the core is
the level: that is the static frame, and it renders with zero JavaScript. The interactive entry adds the pulse: slow and
small when calm, fast and large when strained, so the state is legible without reading a number. This is a low-precision
ambient read; for an exact figure use Progress or Sparkline.
Install
import { BreathingDot } from "@microcharts/react/breathing-dot";<BreathingDot value={0.42} title="Load" />Try it
Motion, and reduced motion
The pulse is the encoding: its rate and amplitude snap to three named bands (calm, elevated, strained). Three,
because ambient motion supports about three discriminable states, and more would be false precision. This is the
deliberate exception to the no-looping-animation rule, allowed because the loop parameter (the rate) is the datum. The
animation is gated twice. 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
Use it 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. For an exact load figure use Progress or Sparkline; for a trend over time, Sparkline. The
boundary with HeartbeatBlip is the data shape: a continuous level is a BreathingDot, discrete arriving events are a
HeartbeatBlip.
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
Edge cases
An unknown value (null or NaN) turns the dot gray, drops its ring, and never pulses, so an unknown system never
renders as a calm one.
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.
Four homes
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.
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; readout={false} drops the chip and keeps everything else.
Props
| Prop | Type | Notes |
|---|---|---|
| value* | 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. |
| size | number | Dot box edge in viewBox units (default 16). |
| fontSize | number | Type size of the percent numeral, in viewBox units. Defaults from `size`. |
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.