That’s it.

If there’s no value attribute on the <progress> element, it’s defined as:

[…]indeterminate, indicating that progress is being made but that it is not clear how much more work remains to be done before the task is complete

HTML Standard §4.10.13 The progress element

Sounds like a loading spinner to me. (Or “loading indicator”. Tomato/tomahto, GIF/JIF.)

<progress> is valid inside almost any element, which makes it very flexible:

Including a loading message with the spinner

<progress> is a labelable element, so you can give it a <label>:


If you don’t want a visible message, but do want an accessible name, try aria-label:

  <progress aria-label="Loading images…"></progress>

Without any styles, this produces a perfectly usable loading indicator everywhere:

This should look like a serviceable loading indicator.

Well, almost. On iOS, the bar looks empty, with no animation. Thanks Safari, but if you include a visible label, the meaning is still clear. And we can still style <progress> on iOS, so let’s fix its garbage.


CSS has a selector for exactly this — the :indeterminate pseudo-class.

  progress:indeterminate {
  /* Styles for your loading indicator go here */

(You can also use progress:not([value]). I’m not your dad.)

By default, interdeterminate <progress> looks like a bar. No problem with that, but I put “spinners” in the title, so you’re probably not satisfied until you see something round what rotates.

Internet Explorer/Edge show a native progress spinner if you set animation-name: -ms-ring:

6 dots spin around in a circle, each one speeding up and slowing down staggered from the others.

You can even change its color with color. Handy! Unfortunately, no other browser has this convenience. Oh well.

Cross-browser styling

First, let’s get the base styles out of the way.

  progress:indeterminate {
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  width: 1em; height: 1em; /* or whatever */
  border: 0;
  background: none;

And then it’s time for the inevitable pseudo-elements. First, Firefox:

  progress:indeterminate::-moz-progress-bar {
  background: none; /* display:none doesn’t work, don’t ask me why */

Then, WebKit/Blink browsers:

  progress:indeterminate::-webkit-progress-bar {
  display: none;

Finally, Internet Explorer and Edge have an odd one:

  progress:indeterminate::-ms-fill {
  animation-name: none; /* Explicitly must be `animation-name: none` */

So that’s the browser styles out of the way, but we need some of our own, right?

  @keyframes spin {
  to { transform: rotate(1turn) }

progress:indeterminate {
  border: 1em double;
  border-color: gray darkgray;
  border-radius: 50%;
  animation: spin 1s infinite;

Putting it all together, we have a loading spinner like this:

If that’s not fancy enough for you, try background-image, or ::before/::after pseudo-elements on the <label>:

579 0 3