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-label or aria-labelledby or aria-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.