Progress
The native progress element as a thin bar in a hue, filled to its value, or striped and moving when it has none.
Example
View Code
<div class="stack" data-gap="md">
<div class="progress" data-scroll aria-hidden="true" data-sticky></div>
<progress class="progress" value="40" max="100" aria-label="Upload">40%</progress>
<p>The thin bar pinned at the top is a reading-progress bar: it fills as this box is scrolled, and it is the one form of progress that is not a progress element, since there is no value to announce.</p>
<p>The bar under it is the ordinary kind, a native progress element with a value, drawn in the hue.</p>
<p>Scroll on and the top bar fills; scroll back and it empties. It follows the reader's own hand and never moves by itself, which is why it stays on under reduced motion.</p>
<p>Where a scroll timeline is unsupported the top bar is not shown at all. A bar that never moved would say the reader had not started.</p>
<h3>A longer read</h3>
<p>A reading-progress bar earns its place on a page with some length to it: an article, a guide, a long settings screen. On a page that fits its screen there is nothing to track, and the bar sits full from the first moment, which says nothing.</p>
<p>It measures the nearest scroll container. At the top of a page that is the page itself. Inside a scroller it is the scroller, so a long panel in a sidebar can carry its own bar without any script telling it which box to watch.</p>
<p>The fill is a scale from the start edge rather than a width, so the browser can run it off the main thread while the page keeps scrolling smoothly; in a right-to-left page it grows from the right, because the start edge is there.</p>
<p>Nothing about the bar is a value. A native progress element would announce forty percent, and forty percent of a page is not something a reader needs read to them while they read, so this form is a plain block hidden from assistive tech.</p>
<p>Give it a hue with the same six words every other component takes, and a thickness with the same sizes. Both apply to the ordinary bar above and to this one alike.</p>
<p>And that is the end of the demo. The bar at the top should be full now, and it empties again as you scroll back up.</p>
</div>
When to use it
Work with a known length: an upload, a wizard's steps, a quota. Leave the value off for work whose length is unknown and the bar says "busy" instead of "40%".
How it works
The native progress element, with its own drawing switched off and a thin rounded track in its place. The filled part is the hue, through the engines' own pseudo-elements, and slides when the value changes. Without a value the element is indeterminate: the track takes diagonal stripes that move along it. data-size sets the thickness to half of the size's space step.
<progress class="progress" data-variant="success" data-size="lg" value="3" max="5" aria-label="Steps">3 of 5</progress>
<progress class="progress" aria-label="Loading">Loading</progress>
Reading progress
data-scroll makes the bar fill with how far the nearest scroll container has been scrolled: the page, when the bar sits at its top, or a scroller, when the bar is inside one. It is the one form of progress that is not a progress element, because there is no value to announce: it is a block with the class, and it carries aria-hidden so a reader is not told about a bar that only mirrors their own scrolling. Pin it with data-sticky as the first child of the page's stack.
<div class="stack">
<div class="progress" data-scroll aria-hidden="true" data-sticky></div>
…
</div>
It is guarded by @supports, and where a scroll timeline is missing the bar is not shown at all: a bar that never moved would say the reader had not started. It is not switched off under reduced motion, because nothing in it moves on its own; the fill follows the reader's hand and stops when they do.
A reading bar at the top of the page usually wants to sit on the edge and be thin: set --yeti-sticky-offset: 0 and --yeti-progress-size: 2px on the bar itself. It paints above the page as it scrolls.
Accessibility
A progress element is a progress bar to assistive tech already; it needs a name, from aria-label or aria-labelledby. Keep the text between the tags current, since some readers announce that rather than the value. An indeterminate bar is announced as busy with no percentage, which is right.
Attributes
| Attribute | Type | Values | Default | Description |
|---|---|---|---|---|
data-variant |
enum | primary, secondary, success, warning, alert, danger, neutral, black, white |
primary |
The hue of the filled part. |
data-size |
enum | sm, md, lg |
md |
The bar's thickness: half the size's space step. |
data-scroll |
boolean | Fill with how far the nearest scroll container has been scrolled, the page when the bar is at its top, instead of with a value: a reading-progress bar. On a block with the class, not a progress element, and hidden from assistive tech; where a scroll timeline is unsupported the bar is not shown. |
Children
No structural requirements.
Tokens
| Token | Description |
|---|---|
--yeti-progress-radius |
Corner of the bar and of its value. |
--yeti-progress-size |
Thickness of the bar; overrides the size step. |
--yeti-color-surface-sunken |
The track. |
--yeti-color-primary |
The default variant's color, when data-variant is absent. |
--yeti-color-primary-subtle |
The default variant's tint. |
--yeti-color-primary-soft |
The default variant's soft stop. |
--yeti-color-primary-strong |
The default variant's strong stop. |
--yeti-color-primary-text |
The default variant's text color. |
--yeti-on-primary |
Text on the default variant's color. |
--yeti-text-md |
The text step when data-size is absent. |
--yeti-space-sm |
The space step when data-size is absent; the bar's height follows it. |
--yeti-duration-base |
How long the value takes to move; one cycle of the indeterminate sweep is four of it. |
--yeti-ease |
The curve of the value's move. |
--yeti-motion-iterations |
How many times the indeterminate sweep repeats; one under reduced motion. |
Internal tokens (may change between minor versions)
- `--_yeti-variant` - `--_yeti-variant-subtle` - `--_yeti-size-space` - `--_yeti-variant-soft` - `--_yeti-variant-strong` - `--_yeti-variant-text` - `--_yeti-on-variant` - `--_yeti-size-text`Accessibility
- Required attributes:
aria-labeloraria-labelledbyoraria-hidden - Put the class on a progress element and give it a name with aria-label or aria-labelledby. Keep its text content current ("40%"), since older assistive tech reads that. Leave out value for work whose length is unknown; the element is then indeterminate and says so. A reading-progress bar has no value to announce, so it carries aria-hidden instead of a name; the rule accepts either.
Browser support
- Used without guards: appearance: none on progress, :dir()
- Behind
@supports: animation-timeline: scroll()
JavaScript
None. This component is CSS only.
Available since 7.0.0.