Spinner

<o-spinner> tells people something is loading, in five variants that share one size, speed and color.

Each variant is a small Orbit universe. The ring is two equal <o-arc>s turning at two paces, so the arc they draw together stretches and shrinks. Dots are satellites on one ring, segments are vectors, and the orbit variant turns two rings of satellites around a core. All of it moves with CSS, so a spinner runs no script while it turns.

Attributes

AttributeDefaultWhat it does
variantringring, dots, segments, orbit or progress.
valueWith variant="progress", how much is done. Without it the progress ring turns like ring; set it and it fills to the value. Remove it to go back.
min / max0 / 100The range of value. See min, max and step.
readoutoffShows the value in the middle of a progress ring: the share done, like 64%, unless a format is set.
labelLoadingThe accessible name: what is loading.
size48pxWidth of the spinner. Everything inside scales with it, from 16px in a button to a full screen.
speed1Seconds per cycle. Each variant keeps its own rhythm around it.
color#4fd1c5Arcs, dots and ticks. Sets --ok-spinner-color.
trackfaint grayThe ring behind the arcs and the orbit lines. Sets --ok-spinner-track.
accent#ffb547The small moons of the orbit variant. Sets --ok-spinner-accent.
count8 dots, 12 segments, 2 moonsHow many dots, ticks or moons.
thickness2, 1.8 for progressRing thickness in lanes. A lane is a twelfth of the radius.
shaperoundedEnding of the arcs: any Orbit shape.
pausedoffStops every animation where it is.
format, decimals, currency, locale, unitNumber format of the readout and aria-valuetext. See Shared options.
themedark, light or glass. Also inherited from an ok-theme-* class on a container. See Shared options.

Properties

NameWhat it does
valueThe progress value, kept in range, or undefined while there is none. Setting it writes the attribute; null removes it.
formatterA function (value, { kind, min, max }) => string for the readout and aria-valuetext. See Shared options.

Progress

When you know how far along a task is, say so. The progress ring turns while there is no value and fills once there is one, gliding from one value to the next.

Styling

PropertyDefaultWhat it does
--ok-spinner-colorvar(--ok-fill, #4fd1c5)Arcs, dots, ticks and the orbit planet.
--ok-spinner-trackvar(--ok-track, rgba(127,127,127,.2))Track and orbit lines.
--ok-spinner-accentvar(--ok-accent, #ffb547)Moons of the orbit variant.
--ok-spinner-speed1sSeconds per cycle.
--ok-spinner-weight1Thickness of the segment ticks.
--ok-font-size17cqminSize of the readout, as a share of the spinner.
Each spinner restyled with an attribute or a custom property.

More examples

Inside a button

A full-screen loader

On a light page

Accessibility

The spinner has role="progressbar" and aria-busy="true", named by label (“Loading” by default; say what is loading). While there is no value it has no aria-valuenow, which is how a progress bar says it does not know how far along it is. With a value it gets aria-valuenow, aria-valuemin, aria-valuemax and an aria-valuetext in the number format, and aria-busy turns false at the end. Set role="status" yourself if the spinner should be announced as it appears; the spinner keeps it. It is never a tab stop.

With prefers-reduced-motion nothing turns or swells. The arcs, dots, ticks and moons stay in place and breathe slowly in and out, so the page still shows it is working, and a progress ring jumps straight to each new value.