Progress
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 is the better fit.
How to use it
Section titled “How to use it”<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 --><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>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:
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:
const bar = document.querySelector<HTMLProgressElement>("#upload")
if (bar) bar.value = 64To 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.
max defaults to 100
Section titled “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:
<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
Section titled “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:
<progress class="lt-progress" aria-label="Fetching your applications"></progress>That is the same thing Spinner says. Choose one of them in a given place, otherwise the page reports the same wait twice.
The colours mean something
Section titled “The colours mean something”<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
Section titled “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:
<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>.thin-bar {--lt-progress-height: var(--lt-spacing-1);--lt-progress-radius: 0;}See Customising for how the variables and the layer work.
Accessibility
Section titled “Accessibility”- Give the bar a name with
aria-label, or tie it to a visible text witharia-labelledby. Without a name the screen reader reports a number without saying what it is about. - Always give
maxtogether withvalue. HTML’s default is1, so a bar withoutmaxis 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.