Progress bars
Progress bars communicate task status visually, giving users a sense of how much work remains. Use them for lengthy processes like file uploads, installations, or multi-step forms to set expectations and reduce uncertainty. Always pair progress bars with clear labels or percentages so users understand what’s happening at a glance.
Progress bars can be determinate (showing specific progress) or indeterminate (indicating ongoing activity without a set endpoint). Choose the right type based on the task: use determinate bars when you can measure progress, and indeterminate bars for processes where timing is unpredictable. For a value that is a measurement rather than a task in progress, such as disk usage or a score, use a meter instead.
The native <progress> and <meter> elements are already announced correctly by screen readers, so they need no extra ARIA attributes. Give each one an accessible name with a <label>. The aria-valuenow, aria-valuemin and aria-valuemax attributes are only needed when you build a custom progress bar from a <div> with role="progressbar".
Default
Use the <progress> element with max and value attributes to create a determinate progress bar. The max attribute sets the total value, while the value attribute indicates the current progress.
<progress max="..." value="..."></progress>
Indeterminate
A progress bar without a value attribute is indeterminate: a process is running, but how much of it is complete is unknown. The whole bar shows moving stripes instead of a filled portion. Set a value as soon as progress can be measured, and the bar switches to the determinate style.
<progress></progress>
Reduced motion: when the visitor has asked their operating system to reduce motion, the stripes stop animating. Pair an indeterminate bar with a short text such as “Uploading…” so the state is clear without the animation.
E-ink: in the e-ink theme the stripes stand still. An indeterminate bar then only shows that something is going on, not that it is still going on; add a text when the wait can be long.
Labels
Connect a <label> to the progress bar with for and id, so assistive technology can announce what the bar belongs to. The text inside the <progress> element is only shown by browsers that cannot display the bar, which makes it a good place for a plain percentage.
<label for="upload-progress">Uploading photo (70%)</label>
<progress id="upload-progress" max="100" value="70">70%</progress>
Colors
Customize progress bar colors using CSS classes to match your design system or brand guidelines. Below are examples of progress bars with different color classes applied.
Catppuccin hues are available for progress bars as well; reference the Catppuccin palette to keep status treatments consistent with the rest of the system.
<progress class="rosewater"></progress>
<progress class="flamingo"></progress>
<progress class="pink"></progress>
<progress class="mauve"></progress>
<progress class="red"></progress>
<progress class="maroon"></progress>
<progress class="peach"></progress>
<progress class="yellow"></progress>
<progress class="green"></progress>
<progress class="teal"></progress>
<progress class="sky"></progress>
<progress class="sapphire"></progress>
<progress class="blue"></progress>
<progress class="lavender"></progress>
<progress class="gray"></progress>
E-ink: in the e-ink theme every progress bar is black, whatever its color class.
Meters
A meter shows a measurement within a known range, such as storage used, a password’s strength or a rating. Unlike a progress bar it doesn’t describe a task that will finish. Set min, max and value, then use low, high and optimum to tell the browser which part of the range is good. The bar turns green, yellow or red depending on where the value falls.
<label for="storage">Storage used: 80 of 100 GB</label>
<meter id="storage" min="0" max="100" low="70" high="90" optimum="0" value="80">80 GB</meter>
| Attribute | Meaning |
|---|---|
min, max | The bounds of the range. They default to 0 and 1. |
value | The current measurement. |
low, high | Split the range into a low, middle and high part. |
optimum | The preferred value. The part of the range that contains it is shown green, the part next to it yellow and the part furthest away red. |
In the example above optimum="0" means that a low value is best. For a value where more is better, such as a battery level, set optimum to the maximum instead.
Demo
Click the button to see the progress bar fill up from 0 to 100%.
const progressBar = document.getElementById("progress-bar");
let value = 0;
const timer = setInterval(() => {
value = Math.min(value + 1, 100);
progressBar.value = value;
if (value === 100) clearInterval(timer);
}, 20);