Skip to content
microcharts
ReferenceExpressiveBreathingDot

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.

BreathingDot
interactive · 3.62 kB · static · 1.98 kB

Install

Import & usereact/breathing-dot
import { BreathingDot } from "@microcharts/react/breathing-dot";<BreathingDot value={0.42} title="Load" />
Needs package + stylesheetSet up with AI

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

bands + the exact-read escape hatch

Edge cases

unknown — gray, no ring, never pulses
out-of-range clamps to the calm/strained edge
NaN reads as unknown, same as null
locale

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

PropTypeNotes
value*numberLevel 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.
sizenumberDot box edge in viewBox units (default 16).
fontSizenumberType 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.

Related charts