EtaBar
React ETA bar chart — remaining time sized by the observed rate, not linear interpolation. Tiny accessible SVG microchart; RSC-safe static or /interactive.
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.
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.