# Progress

> A progress bar for something that has an end. CSS component with one class and no JavaScript.

The class goes on a real `<progress>`. The element has the semantics built in: the browser reports it as `role="progressbar"` and announces how far it has got, so you write neither a role, `aria-valuenow` nor `aria-valuemax`. If you build the bar from `<div>` elements, you have to write all of that yourself, and keep it in step with the number.

If you do not know how far it has got, [Spinner](/components/spinner.md) is the better fit.

## How to use it

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/progress/progress.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css -->
```

```html
<progress class="lt-progress" value="3" max="7"
        aria-label="Application progress" aria-valuetext="Step 3 of 7"></progress>


<progress class="lt-progress" value="100" max="100" data-color="success"
        aria-label="Upload complete"></progress>


<progress class="lt-progress" aria-label="Fetching your applications"></progress>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/progress.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** you spread the attributes in. That is the route in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<progress {...lt.progress({ value: 3, max: 7, label: "Application progress", valueText: "Step 3 of 7" })} />
```

`valueText` becomes `aria-valuetext`, and the screen reader reads it instead of the percentage. `max` is rarely announced, so “43 percent” is all the user gets without it; “Step 3 of 7” says how many steps are left.

The import goes to `/react` here, since the block is JSX. From there the keys are called `className` and `htmlFor`. In Astro and anything else without React you import `@lo-tek/interfaces` and get `class` and `for`.

**If you update the number while something is in progress,** you set the property on the element. That is the browser’s own API, and it is faster than rebuilding the attributes for every step:

```ts
const bar = document.querySelector<HTMLProgressElement>("#upload")


if (bar) bar.value = 64
```

To make the bar indeterminate again, remove the attribute: `bar.removeAttribute("value")`. If you use `lt.setAttributes(bar, lt.progress({ … }))` instead, you pass all the options again: `lt.progress()` manages `value`, `max`, `aria-label` and `aria-valuetext`, so a call without `label` removes the name, and `max` is set to 100 when you do not give it.

See [Type-safe use](/type-safe-use.md).

## `max` defaults to 100

The builder function always writes out `max`, contrary to the rule that a default value gives no attribute. That rule is about appearance, where the CSS already has the answer. `max` is semantics, and HTML’s own default is `1`:

```html
<progress value="40"></progress>
```

That bar is full, not 40 percent, and nothing tells you. If you write the markup by hand, give both.

## Indeterminate progress

If you leave out `value`, the bar becomes indeterminate, and a stripe sweeps across it. It says “something is in progress” without promising a number:

```html
<progress class="lt-progress" aria-label="Fetching your applications"></progress>
```

That is the same thing [Spinner](/components/spinner.md) says. Choose one of them in a given place, otherwise the page reports the same wait twice.

## The colours mean something

**Plain HTML**

```html
<progress class="lt-progress" value="72" max="100"
        aria-label="Uploading attachment"></progress>


<progress class="lt-progress" value="100" max="100" data-color="success"
        aria-label="Upload complete"></progress>


<progress class="lt-progress" value="88" max="100" data-color="warning"
        aria-label="Storage is almost full"></progress>


<progress class="lt-progress" value="18" max="100" data-color="danger"
        aria-label="Upload stopped"></progress>
```

| `data-color` | Used for |
| - | - |
| *(omitted)* | Something is in progress, and it is going as it should |
| `success` | It is finished |
| `warning` | It is approaching a limit |
| `danger` | It stopped, or went wrong |

The colour alone says nothing. Someone who cannot tell green from red needs the text next to it, and someone listening to the page only gets the `aria-label`. So write what happened, and let the colour confirm it.

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-progress` | The bar. The colour comes from `data-color`. |

| Variable | Default |
| - | - |
| `--lt-progress-fill` | `var(--lt-color-accent-fill)`, and `data-color` changes it |
| `--lt-progress-track` | `var(--lt-color-neutral-surface)` |
| `--lt-progress-border` | `var(--lt-color-neutral-border)`, the border around the track. Set it to `transparent` to remove it |
| `--lt-progress-height` | `var(--lt-spacing-2)` |
| `--lt-progress-radius` | `var(--lt-spacing-1)` |

The fill colour is carried by `color` on the bar itself, and the filled part reads `currentColor`. The reason is that the filled part has two names, `::-webkit-progress-value` in Chromium and WebKit and `::-moz-progress-bar` in Gecko, and they cannot be in the same selector list: a pseudo-element the engine does not know makes the whole rule invalid. The colour therefore has to be decided in one place and inherited. `<progress>` has no text, so `color` is free for the job, and `--lt-progress-fill` is all you need to set to change it.

If the user has asked for reduced motion, the indeterminate stripe moves more slowly, and the filled part jumps to the new number instead of sliding there. The stripe does not stop completely: an indicator that stands still no longer says that something is in progress.

An example. A bar under a field should often be thinner and without corners:

**Plain HTML**

```html
<progress class="lt-progress" value="60" max="100"
        aria-label="Default"></progress>


<progress class="lt-progress thin-bar" value="60" max="100"
        aria-label="Uploading attachment"></progress>
```

```css
.thin-bar {
--lt-progress-height: var(--lt-spacing-1);
--lt-progress-radius: 0;
}
```

See [Customising](/customising.md) for how the variables and the layer work.

## Accessibility

- Give the bar a name with `aria-label`, or tie it to a visible text with `aria-labelledby`. Without a name the screen reader reports a number without saying what it is about.
- Always give `max` together with `value`. HTML’s default is `1`, so a bar without `max` is full at anything above 1.
- Do not update the number more often than the user needs. Every change can become an announcement, and a bar that counts from 0 to 100 in steps of one becomes a hundred messages.
- When the progress is over, say so with more than the colour. Move focus to where the result appeared, or report it in a `role="status"` element.
