EtaBar
React ETA bar chart — remaining time sized by the observed rate, not linear interpolation. Tiny accessible SVG microchart; RSC-safe static or /interactive.
- encodes
- time axis — elapsed vs predicted-remaining
- precision
- high / medium
- nodes
- ≤ 4
EtaBar answers "how long is this actually going to take, given how it has actually been going?". Its x-axis is time, not fraction: the solid part is the elapsed share of the predicted total, and the muted remainder is sized by the observed rate. When the rate drops, the remainder honestly grows — the download bar, told truthfully.
Install
import { EtaBar } from "@microcharts/react/eta-bar";<EtaBar progress={0.64} elapsed={3.6} rate={0.18} etaFormat={(t) => `${Math.round(t)} min`} title="Export" />Try it
When to use it
- Good for — download / export progress, job-queue ETAs, deploy timers.
- Avoid for — fraction-only progress (Progress) or unbounded counters (Delta).
Sizing
Motion, and reduced motion
The interactive entry eases the elapsed/remainder split (SVG x/width) to its new geometry whenever progress,
elapsed, or rate change — the split only ever moves to a value computed from data you passed in on that render; it
never interpolates ahead of an as-yet-unseen future frame. The transition lives in shared CSS (.mc-eta-live rect), so
it inherits the shared :where(.mc-root *) { transition: none } reduced-motion block: with
prefers-reduced-motion: reduce the bar snaps straight to each new split instead of easing. The live region
re-announces the forecast on a throttle (announceEvery, default 10 s) so a fast-updating value doesn't spam a screen
reader.
Variants
Without an etaFormat, the remaining-time figure falls back to format/locale — under de-DE that's a decimal comma
("11,6") rather than the English decimal point ("11.6"). Pass etaFormat (as the hero example does) to add a unit; it
always receives the raw number, so it can format the unit itself in whatever locale-aware way the caller needs.
Edge cases
An explicit rate of 0, or an absent rate from which no positive average can be derived from elapsed, renders the
remainder as a diagonal-hatched texture instead of a solid bar — "unknown," not a hidden guess — and the summary says
"stalled" rather than naming a duration. When the predicted remainder is far larger than the elapsed time, the done
segment would round to an unreadable sliver, so the geometry clamps it to a visible 10% and adds a chevron marking the
clamp as approximate. At progress >= 1 the bar fills completely regardless of rate.
Four homes
Why this default
The ETA label is the default because "how long?" is the question a plain progress bar pretends to answer. The remainder is sized by the observed rate, never linear interpolation — so a stalling transfer looks stalled instead of marching to a fake finish. When the rate is unknown or zero the remainder becomes an indeterminate texture and the summary says so, rather than inventing a countdown.
Accessibility
The accessible name is the honest forecast — "42% done; about 11.6 remaining at the current rate." A stalled transfer reads "30% done; stalled." and a finished one "Done." The interactive entry re-announces on a throttle as the forecast changes.
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 |
|---|---|---|
| progress* | number | Completed fraction 0–1. |
| elapsed* | number | Time spent, any unit. |
| rate | number | Progress per time unit — pass a recent-window rate. |
| label | "eta" | "percent" | "none" | The remaining-time read is the product. |
| etaFormat | (t: number) => string | Unit-bearing ETA label ("2 min") — the caller owns units. |
| announceEvery | number | interactiveMinimum ms between live-region announcements as the ETA streams (default 10000). |
| animate | boolean | interactiveOpt-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.
Related charts
Hypnogram
React hypnogram chart — categorical step strip of which discrete state held over time. Tiny accessible SVG microchart; RSC-safe static or /interactive.
Waveform
React waveform chart — signal shape compressed by max-per-bucket at word width. Tiny accessible SVG microchart; RSC-safe static or /interactive.