Choose the state that matches the task

A determinate indicator shows how much of a task is complete. Use one when you know both the amount completed and the total: for example, 30 files processed out of 100. An indeterminate indicator says that work is underway but does not claim to know how much remains. It is a better fit while waiting for a server response whose duration or total work is unknown.

Do not turn elapsed time into a percentage unless time really defines completion. A bar that climbs toward 95% and then waits can make a healthy task look broken. If a task has several phases, decide whether you can measure the entire job. Otherwise, show the current phase in words, such as “Preparing files,” rather than presenting a misleading overall percentage.

Use a native progress element when possible

For a straightforward task, the browser’s progress element provides a useful starting point. Give it a visible label that identifies the task, such as “Uploading photos,” rather than a vague label such as “Progress.” When the task is determinate, set a maximum and a value in the same units. If the maximum is 100 and the value is 40, the element represents 40% completion. When the amount is unknown, leave its value unset; setting the value to zero instead describes a determinate task that has not advanced.

The following JavaScript assumes a modern browser page with a progress element stored in a variable named progress. A determinate update can use progress.max = 100; progress.value = 40;. To return it to an indeterminate state, use progress.removeAttribute('value');. Those statements only demonstrate the state change: the page still needs a label, task data, and a clear completion state. Native appearance varies by browser, so check that any styling preserves a visible track and enough contrast to distinguish the filled portion.

Connect the indicator to real measurements

For a task with measurable units, calculate the value from completed units divided by total units. Suppose an application processes an array of images one at a time. Before starting, set the maximum to images.length. After each image finishes processing, increase the value by one. This reports completed work, not merely work that has started. If the list is empty, skip the progress phase and report that there is nothing to process; a zero maximum cannot communicate a useful fraction.

Keep displayed text consistent with the indicator. If the label says “3 of 10 images processed,” the value should be 3 and the maximum should be 10. If work can fail or be retried, define what counts as complete before updating the number. A failed item might be counted as processed if the task is to attempt every item, but not if the task is to upload every item successfully. Make that distinction clear in the accompanying text.

Make changes understandable without creating noise

A visible label explains what the indicator measures, while nearby text can explain its current state. People who cannot see an animated spinner also need to know that a task has started and whether it finished. Use a short status message, such as “Upload started” and later “Upload complete,” in a polite status region. Avoid announcing every small percentage change: frequent updates can interrupt other information and become difficult to follow.

For a custom visual indicator, use progressbar semantics and an accessible name. Supply a current value and minimum and maximum only when progress is determinate. Omit the current value when it is indeterminate; a made-up zero suggests a measurement that does not exist. A custom component takes more work to implement and test than the native element, so choose it when its behavior or presentation is genuinely needed, not just to replace the browser’s default styling.

Handle completion, errors, and uncertain waits

Progress is only one part of the task lifecycle. At completion, show a clear result rather than leaving an apparently active indicator on screen. If the task fails, replace “Working” with an error message that says what can be tried next. If cancellation is supported, provide a separate, clearly labeled control and make sure it actually stops or dismisses the work as promised.

Test both fast and slow paths in a modern browser: a task that finishes immediately, one with several updates, one whose total is unknown, and one that fails. Check the visible text alongside the indicator, then use a keyboard and a screen reader to confirm that the task name and outcome are understandable. The goal is not constant motion. It is an honest answer to two questions: what is happening, and how much can the application reliably say about its progress?

Back to category ↗