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

EtaBar takes a job's `progress`, `elapsed` time, and observed `rate` and shows how long is left at that pace. Its axis
is time rather than fraction: the solid part is the elapsed share of the predicted total, and the muted remainder is how
long the rest will take at the current rate. When the rate drops, the remainder grows instead of marching to a fixed
finish. The ETA label is on by default; `label="percent"` shows the fraction instead.

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

## Try it

```tsx
import { EtaBar } from "@microcharts/react/eta-bar/interactive";

<EtaBar
  progress={0.64}
  elapsed={3.6}
  rate={0.18}
  etaFormat={(t) => `${Math.round(t)} min`}
/>
```

## When to use it

Use it for download and export progress, job-queue ETAs, and deploy timers. For fraction-only progress use Progress; for
unbounded counters use Delta.

## Sizing

**table cell**

```tsx
<EtaBar progress={0.64} elapsed={128} rate={0.5} width={60} height={8} />
```

**stalled**

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

## 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 toward a frame it hasn't seen. The transition lives in shared CSS (`.mc-eta-live rect`), so it
inherits the shared `:where(.mc-root *) { transition: none }` reduced-motion block: under
`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 you need.

## 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. That texture means unknown, 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

**In a sentence**

```tsx
<p>
  Export progress{" "}
  <span className="mc-inline">
    <EtaBar progress={0.64} elapsed={3.6} rate={0.18} summary={false} />
  </span>{" "}
  — 64% done, ~2 min remaining.
</p>
```

**In a table cell**

```tsx
<td>
  <EtaBar progress={0.64} elapsed={3.6} rate={0.18} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">64%</span>
  <span className="unit">~2 min left</span>
  <EtaBar progress={0.64} elapsed={3.6} rate={0.18} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  report <EtaBar progress={0.64} elapsed={3.6} rate={0.18} />
</button>
```

## Accessibility

The accessible name is the 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).
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 | 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).
