Progress Indicators

v1.0.0

Native progress bars and decorative spinners for loading states.

Determinate progress

Use a labelled native <progress> when completion can be measured. Keep value, max and any visible value text synchronized in application code.

64%
<label for="upload-progress">Uploading files</label>
<progress class="progress" id="upload-progress" max="100" value="64">64%</progress>

The native element supplies progressbar semantics and its current value. Do not add role="progressbar" or duplicate aria-valuenow.

Indeterminate progress

Omit value when an operation is active but its completion cannot be measured. A missing value is different from value="0".

Synchronizing…
<label for="sync-progress">Synchronizing workspace</label>
<progress class="progress progress-secondary" id="sync-progress" max="100">Synchronizing…</progress>

To switch an existing progress bar to indeterminate, remove its value attribute and property state. Reduced-motion preferences keep a static value segment instead of moving it.

Spinner

Use a spinner for a short operation when neither a percentage nor final content geometry is known. The spinner is decorative: hide it from assistive technology and put the loading message in a separate status.

Saving changes…
<div class="d-flex align-items-center gap-2" role="status">
	<span class="spinner" aria-hidden="true"></span>
	<span>Saving changes…</span>
</div>

Do not give the decorative element a progressbar role. Keep the text visible when possible; if an existing labelled control already communicates the state, avoid adding another live announcement.

Sizes and layouts

Use -sm, -md or -lg sizes. Progress bars are block-width by default; .progress-inline uses –progress-inline-size. Spinners are inline by default; .spinner-block establishes a block layout.

Small 35%
Medium 55%
Large 75%
45%

Semantic colors

Color supports context but must not be the only status cue. Keep the label or adjacent status text meaningful.

72%
72%
72%
72%
72%

Choosing a loading pattern

  • Use progress for measurable completion, or its indeterminate state when the same operation temporarily has no usable estimate.
  • Use a spinner for short, compact activity without known content geometry.
  • Use a Skeleton when placeholders can reserve the final content geometry and prevent layout shift.
  • Expose one loading status per operation. Do not combine progress, a spinner and a skeleton in a way that announces the same state more than once.

CSS Variables

Override these variables on the component or an ancestor.

--progress-block-size: 0.5rem;
--progress-inline-size: 8rem;
--progress-track-color: var(--bg-active);
--progress-value-color: var(--primary);
--progress-border-color: var(--control-border-color);
--progress-border-width: var(--border-width);
--progress-border-radius: 9999px;
--progress-animation-duration: 1.5s;
--progress-animation-easing: ease-in-out;

--spinner-size: 1rem;
--spinner-border-width: 0.125rem;
--spinner-track-color: color-mix(in srgb, var(--spinner-color) 25%, transparent);
--spinner-color: var(--primary);
--spinner-animation-duration: 0.75s;
--spinner-animation-easing: linear;