# EtaBar (/docs/charts/eta-bar)

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.

```tsx
<EtaBar
  progress={0.64}
  elapsed={3.6}
  rate={0.18}
  etaFormat={(t) => `${Math.round(t)} min`}
  title="Export"
  width={280}
  height={16}
/>
```

## Install

```tsx
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" />
```

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


## When to use it

- **Good for** — download / export progress, job-queue ETAs, deploy timers.
- **Avoid for** — fraction-only progress (Progress) or unbounded counters (Delta).


## 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

```tsx
<EtaBar progress={0.42} elapsed={10} rate={0.05} label="percent" />
```

```tsx
// no etaFormat → the remaining-time NUMBER (not a unit string) follows locale
<EtaBar progress={0.42} elapsed={10} rate={0.05} locale="de-DE" />
```

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

```tsx
<EtaBar progress={0.3} elapsed={40} rate={0} />
```

```tsx
// predicted total ≫ elapsed → the done share clamps to a visible 10%
// sliver plus a chevron, instead of shrinking to an unreadable hairline
<EtaBar progress={0.08} elapsed={30} rate={0.001} />
```

```tsx
<EtaBar progress={1} elapsed={12} rate={0.1} />
```

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


## 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](/docs/accessibility#one-interaction-contract).

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `progress` (required) | `number` | Completed fraction 0–1. |
| `elapsed` (required) | `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` | (interactive) Minimum ms between live-region announcements as the ETA streams (default 10000). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
