Progress
Waiting is an experience we design. Tell the user something's happening, and if possible, how long it will take. Never leave them guessing.
Linear bar
For processes with a known duration — uploads, imports, broker scans. The fill animates smoothly from its last value, never jumps.
When you don't know how long
For short waits where a percentage would be misleading. Under 1 second, prefer nothing at all — the motion is often more distracting than the wait.
Progress ring
For compact surfaces — dashboard tiles, mobile list rows. Pair with a numeric value; the ring alone is too precise to read at a glance.
Multi-step flows
For onboarding and checkout. Each segment is one step; accent represents completed, muted represents upcoming.
For content that's loading
Use when the layout is stable but the content is arriving. The skeleton matches the final content's shape — never generic grey rectangles in the wrong size.
Picking the right one
Known duration → determinate.
If the backend tells you percentage, always show it. Even coarse estimates are better than a spinner.
Sub-second wait → nothing.
Flashing a spinner for 300ms reads as a bug. Debounce loaders to appear only after 500ms of delay.
Layout will shift → skeleton.
If the final content will push things around, show the skeleton so users can orient before data lands.
Never stack loaders.
A page spinner plus three row spinners plus a skeleton is chaos. One load state per surface.