progress
Displays the completion progress of a task. Use for file uploads, form steps, loading states, or any operation with a known endpoint. For scalar measurements (like disk space), use meter instead.
When to Use
- File upload progress
- Download progress
- Multi-step form completion
- Loading states with known duration
- Installation or setup progress
Use meter instead for static measurements like disk usage, battery level, or scores.
Basic Usage
A determinate progress bar shows completion percentage.
<label>Upload Progress</label><progress value="70" max="100">70%</progress> <label>Step 3 of 5</label><progress value="3" max="5">60%</progress>
Attributes
| Attribute | Purpose | Default |
|---|---|---|
value |
Current progress value | Indeterminate if omitted |
max |
Maximum value (completion) | 1 |
Indeterminate Progress
Omit the value attribute for tasks with unknown duration. Shows an animated loading state.
<!-- No value attribute = indeterminate --><progress>Loading...</progress>
Size Variants
Different heights for various contexts.
<progress class="xs" value="60" max="100">60%</progress><progress class="s" value="60" max="100">60%</progress><progress value="60" max="100">60%</progress> <!-- default --><progress class="l" value="60" max="100">60%</progress>
Color Variants
Semantic colors for different states or contexts.
<progress value="100" max="100">Complete</progress><progress class="success" value="100" max="100">Complete</progress><progress class="warning" value="60" max="100">60%</progress><progress class="error" value="30" max="100">30%</progress>
Labeled Progress
Use the .labeled wrapper pattern for progress bars with percentage displays.
<section class="labeled"> <label> <span>Uploading file.zip</span> <span>45%</span> </label> <progress value="45" max="100">45%</progress></section>
Recipes
Reading Progress Indicator
A fixed-position progress bar at the top of the page that tracks scroll position. Uses a single passive scroll listener and one attribute update.
<progress id="reading-progress" max="100" value="0" aria-hidden="true"></progress> <script> const bar = document.getElementById('reading-progress'); document.addEventListener('scroll', () => { const { scrollTop, scrollHeight, clientHeight } = document.documentElement; bar.value = (scrollTop / (scrollHeight - clientHeight)) * 100; }, { passive: true });</script>
Indeterminate as Skeleton Shimmer
An indeterminate <progress> (no value attribute) triggers the browser's native animation. Override the pseudo-element backgrounds for a shimmer effect — zero JS, zero extra DOM. See the skeleton loading pattern for the full approach using data-loading="skeleton".
Ring Mode
Add data-type="ring" to render a circular progress indicator. Without JavaScript, it renders as a standard linear bar (progressive enhancement). With JS, the progress-ring-init utility enhances it to a circle with an auto-synced center label.
<progress data-type="ring" value="75" max="100">75%</progress>
Ring Sizes
Use data-size for different ring diameters.
<progress data-type="ring" data-size="xs" value="60" max="100">60%</progress><progress data-type="ring" data-size="s" value="60" max="100">60%</progress><progress data-type="ring" data-size="m" value="60" max="100">60%</progress><progress data-type="ring" data-size="l" value="60" max="100">60%</progress><progress data-type="ring" data-size="xl" value="60" max="100">60%</progress>
Ring Colors
Semantic color classes work on rings the same as linear bars.
<progress data-type="ring" value="85" max="100">85%</progress><progress data-type="ring" class="success" value="100" max="100">Done</progress><progress data-type="ring" class="warning" value="45" max="100">45%</progress><progress data-type="ring" class="error" value="15" max="100">15%</progress>
Ring Indeterminate
Omit value for a spinning animation. No extra attributes needed — native indeterminate state is detected automatically.
<progress data-type="ring" data-size="l" aria-label="Loading">Loading...</progress>
Custom Labels
By default, the center label shows the percentage. Use data-label to override it, or data-label="none" to hide it.
<!-- Custom label text --><progress data-type="ring" data-label="42/100" value="42" max="100">42 of 100</progress> <!-- No label --><progress data-type="ring" data-size="xs" data-label="none" value="60" max="100">60%</progress>
Ring Attributes
| Attribute | Values | Description |
|---|---|---|
data-type="ring" |
ring |
Enables circular mode |
data-size |
xs, s, m, l, xl |
Ring diameter (default: m) |
data-label |
text or none |
Override or hide center label |
Ring Custom Properties
| Property | Default | Description |
|---|---|---|
--progress-ring-size |
4em |
Ring diameter |
--progress-ring-width |
0.35em |
Track thickness |
Updating with JavaScript
Just set the native value property — the JS utility auto-syncs the --progress custom property and center label via MutationObserver.
const ring = document.querySelector('progress[data-type="ring"]');ring.value = 85; // That's it — label and visual update automatically/code-block </section> <section> <h2>Progress vs Meter</h2> <table class="props-table"> <thead> <tr> <th>Use progress</th> <th>Use meter</th> </tr> </thead> <tbody> <tr> <td>Tasks in progress</td> <td>Static measurements</td> </tr> <tr> <td>File uploads, downloads</td> <td>Disk space, battery level</td> </tr> <tr> <td>Has determinate/indeterminate states</td> <td>Has optimum value ranges</td> </tr> <tr> <td>Value changes over time</td> <td>Value is a snapshot</td> </tr> <tr> <td>No color change based on value</td> <td>Color changes based on optimum</td> </tr> </tbody> </table> </section> <section> <h2>Accessibility Notes</h2> <ul> <li><strong>Include fallback text</strong>: Content between tags is shown if progress is not supported</li> <li><strong>Use labels</strong>: Always associate a label with the progress element</li> <li><strong>Announce updates</strong>: Consider using <code>aria-live</code> for important progress updates</li> <li><strong>Provide context</strong>: Include percentage or step numbers in visible text</li> <li><strong>Screen reader support</strong>: Progress elements are announced as "X% complete"</li> </ul> </section> <section> <h2>CSS Reference</h2> <code-block language="css" show-lines label="Progress CSS styles" data-escape>/* Tokens (defined in src/tokens/forms.css) */:root { --progress-h: var(--size-s); --progress-track-bg: var(--color-surface-raised); --progress-fill: var(--color-interactive);} progress { appearance: none; display: block; inline-size: 100%; block-size: var(--progress-h); border: none; border-radius: var(--radius-full); overflow: hidden; background: var(--progress-track-bg);} /* WebKit/Blink progress bar */progress::-webkit-progress-bar { background: var(--progress-track-bg); border-radius: var(--radius-full);} progress::-webkit-progress-value { background: var(--progress-fill); border-radius: var(--radius-full); transition: inline-size var(--duration-normal) var(--ease-out);} /* Firefox */progress::-moz-progress-bar { background: var(--progress-fill); border-radius: var(--radius-full);} /* Size variants */progress.xs { block-size: var(--size-3xs); }progress.s { block-size: var(--size-2xs); }progress.m { block-size: var(--size-s); }progress.l { block-size: var(--size-m); } /* Color variants — now use semantic tokens */progress.success::-webkit-progress-value { background: var(--color-success); }progress.warning::-webkit-progress-value { background: var(--color-warning); }progress.error::-webkit-progress-value { background: var(--color-error); } /* Indeterminate animation */progress:indeterminate { animation: progress-indeterminate 1.5s ease-in-out infinite;}