# ProgressBar

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/progressbar/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

A bar that shows how far a job has come, or that it runs while nobody knows how far (also called a progress indicator). Reach for it for imports, uploads and anything else that counts towards an end.

```ts
import { ProgressBar } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/progressbar/

`value` runs from 0 to 1; values outside are clamped. A screen reader hears the percentage, or `valueText` where a count says it better.

It has no tone: progress is neither good nor bad, and a job at 90 % is not a warning.

## Examples

### How far a job has come

A `value` from 0 to 1 and a `label` naming what progresses; `showLabel` puts the percentage beside the bar.

```tsx
import { ProgressBar } from "@umriss-ui/core";

export default function HowFar() {
  return (
    <div style={{ maxWidth: 420 }}>
      <ProgressBar value={0.42} label="Import of the March invoices" showLabel />
    </div>
  );
}
```

### Count instead of a share

Where a count says it better, pass it as `valueText`: a screen reader hears “5 of 12 parcels” instead of a percentage.

```tsx
import { ProgressBar, Stack, Text } from "@umriss-ui/core";

export default function ACount() {
  return (
    <Stack gap={2} style={{ maxWidth: 420 }}>
      <Text size="sm">5 of 12 parcels scanned onto FP 214 K</Text>
      <ProgressBar value={5 / 12} label="Loading of tour T-01" valueText="5 of 12 parcels" />
    </Stack>
  );
}
```

### Run without a known end

Leave out `value` when nobody knows how far it is; the bar runs and shows no figure.

```tsx
import { ProgressBar, Stack, Text } from "@umriss-ui/core";

export default function WithoutAnEnd() {
  return (
    <Stack gap={2} style={{ maxWidth: 420 }}>
      <Text size="sm">Waiting for the payment provider to confirm …</Text>
      <ProgressBar label="Waiting for the payment provider" />
    </Stack>
  );
}
```

### Follow a job through its steps

One bar per step and one for the whole, derived from the steps; the step waiting on a person runs without a figure.

```tsx
import { useState } from "react";
import { Badge, Button, Card, CardBody, CardHeader, Divider, ProgressBar, Stack, Text } from "@umriss-ui/core";

interface Step {
  name: string;
  /** Items to work through; 0 for a step that waits on someone. */
  items: number;
  unit: string;
}

const STEPS: Step[] = [
  { name: "Import the bank statements", items: 4, unit: "accounts" },
  { name: "Match payments to invoices", items: 312, unit: "payments" },
  { name: "Controller's sign-off", items: 0, unit: "" },
  { name: "Post to the ledger", items: 9, unit: "cost centres" },
];

/** The share done per step after `ticks` updates from the server. */
function sharesAfter(ticks: number): number[] {
  let left = ticks;
  return STEPS.map((step) => {
    const needed = step.items === 0 ? 2 : 4;
    const taken = Math.min(left, needed);
    left -= taken;
    return taken / needed;
  });
}

export default function ACloseInSteps() {
  const [ticks, setTicks] = useState(6);
  const shares = sharesAfter(ticks);
  const overall = shares.reduce((sum, share) => sum + share, 0) / STEPS.length;
  const running = shares.findIndex((share) => share < 1);

  return (
    <Card style={{ maxWidth: 560 }}>
      <CardHeader
        eyebrow="Month-end close"
        title="February 2026"
        actions={<Badge>{running === -1 ? "Done" : `Step ${running + 1} of ${STEPS.length}`}</Badge>}
      />
      <CardBody>
        <Stack gap={4}>
          <ProgressBar value={overall} label="The whole close" showLabel />
          <Divider />
          {STEPS.map((step, i) => {
            const share = shares[i]!;
            const waiting = step.items === 0 && i === running;
            return (
              <Stack key={step.name} gap={1}>
                <Text size="sm">{step.name}</Text>
                {waiting ? (
                  <ProgressBar label={step.name} />
                ) : (
                  <ProgressBar
                    value={share}
                    label={step.name}
                    showLabel
                    valueText={step.items > 0 ? `${Math.round(share * step.items)} of ${step.items} ${step.unit}` : undefined}
                  />
                )}
              </Stack>
            );
          })}
          <Stack direction="row" gap={2}>
            <Button onClick={() => setTicks((t) => Math.min(t + 1, 14))}>Advance</Button>
            <Button variant="ghost" onClick={() => setTicks(0)}>
              Start again
            </Button>
          </Stack>
        </Stack>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- A measured share read against a bound: utilisation, budget used → Meter
- A short wait with nothing to count → Spinner

## Keyboard

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## Accessibility

The role `progressbar`, named by `label` - "Progress" if you give none - or by your `aria-labelledby`. With a `value` it carries the whole percentage as `aria-valuenow`, and `valueText` as `aria-valuetext`; without one it carries no value, the state a screen reader knows as indeterminate.

It is no live region: a screen reader reads the value when it reaches the bar, and a job that finishes announces nothing. Say the end in words, in an [Alert](https://romanhaendler.github.io/umriss-ui/core/alert/) or a [Toast](https://romanhaendler.github.io/umriss-ui/core/toast/).

Under forced colours the track takes an edge and the fill the system's selection colour. With reduced motion the sweep of a bar without a value runs three times slower.

## API

### `ProgressBarProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `value` | `number` | — | How far the task has come, from 0 to 1. Left out, the bar is indeterminate: the task runs, and nobody knows how far it is.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/progressbar/#configurator-progressbar), [How far a job has come](https://romanhaendler.github.io/umriss-ui/core/progressbar/#how-far), [Count instead of a share](https://romanhaendler.github.io/umriss-ui/core/progressbar/#a-count) and 3 more |
| `label` | `string` | the wording's "Progress" | What is progressing, e.g. "Import of the invoice records". A progress bar needs a name from the author.<br>Shown in: [How far a job has come](https://romanhaendler.github.io/umriss-ui/core/progressbar/#how-far), [Count instead of a share](https://romanhaendler.github.io/umriss-ui/core/progressbar/#a-count), [Run without a known end](https://romanhaendler.github.io/umriss-ui/core/progressbar/#without-an-end) and 3 more |
| `valueText` | `string` | — | What a screen reader says instead of the percentage, where a count says it better - "3 of 12 files".<br>Shown in: [Count instead of a share](https://romanhaendler.github.io/umriss-ui/core/progressbar/#a-count), [Follow a job through its steps](https://romanhaendler.github.io/umriss-ui/core/progressbar/#a-close-in-steps), [A deployment (Stepper)](https://romanhaendler.github.io/umriss-ui/core/stepper/#a-deployment) |
| `showLabel` | `boolean` | `false` | The percentage beside the bar (Geist Mono). Never for an indeterminate bar - it has no figure to show.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/progressbar/#configurator-progressbar), [How far a job has come](https://romanhaendler.github.io/umriss-ui/core/progressbar/#how-far), [Follow a job through its steps](https://romanhaendler.github.io/umriss-ui/core/progressbar/#a-close-in-steps) and 1 more |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<div>`.

## Known limits

- No colour of verdict and no steps of its own; for a job in steps, stack one bar per step.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
