The progress Tag
The progress tag draws a bar that fills as a task completes. Uploads, downloads, file processing, form wizards: whenever there is a known amount of work and an amount done, progress is the right element. You set value for the work done and max for the total.
Leave the value attribute off and the bar switches to an indeterminate mode, an animated stripe that moves back and forth. That is the correct look when the task has no measurable end, like a video buffering or a page waiting on a slow network request.
Why it exists: loading bars were built from divs and CSS animations for years. Progress gives the browser a native bar that screen readers understand, and JavaScript can update it live with a single property assignment. No drawing code, no width math.
Updating it live
Progress shines when a script updates it. You move the bar by setting its value property, and the browser repaints. A common mistake is rebuilding the element with innerHTML on every update; assigning the value property is cheaper and keeps any styling intact.
const bar = document.getElementById("load");
bar.value = 40;
Indeterminate mode
Omit the value attribute and the bar animates back and forth. This is the right look when the task has no measurable end. Screen readers get a busy indicator instead of a wrong percentage. Once the task becomes measurable, set value and max and the bar settles into determinate mode.
<progress>Loading...</progress>
Syntax
<progress value="70" max="100">70 percent</progress>
Omit value for Spinner, But Label It or Screen Readers Hear '35%' With No Context
Two traps: 1) To get an indeterminate spinner you must REMOVE the value attribute completely (element.removeAttribute('value')), setting value=0 makes a 0% bar, not spinner. 2) A bare <progress value="35"> is announced as '35 percent' with no idea what is loading - MDN and W3docs stress you must use <label> or aria-label. Text between tags is only fallback for ancient browsers, not accessible name.
<label for="dl">Downloading:</label><progress id="dl" value="35" max="100">35%</progress>
// indeterminate: <progress aria-label="Loading"></progress>
Good to know
- value and max control the fill amount.
- No value attribute means indeterminate.
- Update bar.value from JavaScript, never innerHTML.
- Pair it with fetch events for real uploads.