Spinners

Spinners are what an interface shows while it waits: a shape moving at a constant rate to say work is in flight without pretending to know when it ends. This family carries both halves of waiting. Three indeterminate sweeps, built from eight arms, eight dots, and a pair of opposed arcs over a soft ring, sit beside a progress ring small enough to live in a list row, a queue of bars with a total and an estimate, a rolling percentage for a blocking wait, and a panel that names each step as it runs.

Choosing between them is a question about duration, not style. A wait too short to notice is better served by nothing at all, since a spinner that appears and vanishes reads as a glitch. A wait long enough for someone to wonder whether it has hung needs more than a sweep, because a spin that never changes looks identical to a freeze. That is where elapsed time, a percentage, or a named step list earns its place.

Spinner

Best for

Work with no measurable end: a request in flight, a permission check

Anatomy

Eight arms · eight dots · a soft ring under two opposed arcs · all three sweeping at a constant rate · a hint message under each

Preparing your workspace

Syncing 12 projects

Checking permissions

Progress circle

Best for

Limits and quotas: a ring small enough to sit in a row and still carry its own number

Anatomy

Plan card with its badge · a row per limit: ring, name, used of total · 6.72px track on a 20.64px radius, round-capped from twelve o'clock · the percentage inside, or not

Plan usage

Resets on 1 October

Pro
  • 60%
    Seats12 of 20
  • 48%
    Storage48 of 100 GB
  • 12%
    API calls6.2k of 50k

Progress bar

Best for

A long job with a real end: an import, a migration, a download

Anatomy

Queue card with its status badge · a row per item: name, size, and queued / percent / done · full-round 6px track with the brand fill · a footer carrying the total and the estimate

Importing from Notion

Started a few seconds ago

Running
  • Pages1,204 items75%
  • Databases38 itemsQueued
  • Attachments3.2 GBQueued

4,812 rows · about 2 minutes left

Percentage indicator

Best for

The one number a full-screen or blocking wait is about

Anatomy

A 0-9 column per place, rolling only when its own digit turns, faded at the slot edges · the heading and what to expect under it · the one way out

25 percent%

Rendering your export

Keep this tab open: the file downloads on its own when it’s ready.

Processing panel

Best for

A job with named steps, where the wait needs to say what it is doing, not just that it is

Anatomy

Title and elapsed time across the full header · a step list marking done, running and pending · one segmented bar and its readout under it, all off one value

Generating September report

Started a few seconds ago

  1. Collecting transactions18,204 records
  2. Reconciling against ledger4,812 rows
  3. Rendering the PDF36 pages
25%
01

Using Spinners

Reach for it when

  • A request whose duration you cannot estimate: an auth check, a permission lookup
  • A button that has been submitted and must not be pressed a second time
  • A quota ring small enough to sit inside a list row and still carry its number
  • A blocking wait where one number is the entire message

Reach for something else when

  • Content arriving into a known layout: a skeleton holds the shape and stops the jump
  • A job with named stages: a step list says which stage is slow; a sweep says nothing
  • A wait short enough to flash: show nothing and let the result land
02

Where it shows up

01

Submit button state

A form button that has been pressed and is waiting on the server. The spinner replaces the label or sits beside it, the button goes disabled, and a second press cannot create a second record.

02

Plan limit rings

A billing page listing each limit with a small ring beside it. The rings are scanned at a glance for the ones near the top, and the used-of-total figure next to each one carries the detail.

03

Import queues

Files processed one after another, each with its own bar and status and a footer totalling the work left. The queue answers which item is slow, which a single aggregate bar cannot.

04

Full-screen migrations

A blocking wait during an account or data move, where the percentage is the whole message. The heading says what is running, the rolling number carries the progress, and one control offers the way out.

03

Questions

What is the difference between a determinate and an indeterminate spinner?

An indeterminate spinner moves at a constant rate and carries no value; it says only that work is happening. A determinate one, a ring or a bar that fills, reports a fraction of a known total. The rule of thumb is that if the fraction cannot be computed honestly, do not draw one, because a determinate ring that stalls is read as a broken product.

Should a loading spinner respect reduced motion preferences?

Yes. A continuous rotation is exactly the sustained motion that the reduced-motion preference exists to suppress. Offer a version that fades or pulses rather than sweeping, or drop the animation and lean on the text beside it. A spinner should not be the only signal that a wait is under way in any case: a line of text naming what is being waited on serves everyone.

All blocks
The Orvain Dispatch

Premium components, free in your inbox.

Join the Orvain Dispatch and get production-ready components, free to keep: React and Vue code plus the Figma frame, yours to ship.

Just components, never spam. You can unsubscribe anytime.