Skip to content
Structure

Loaders & states

A system is judged on its loading states, not its buttons.

Loading is when the user is least patient and most likely to leave. Empty and broken are the screens nobody designs and everybody sees.

Which loader, and when

Match the shape of the wait, not the taste of the designer.

DurationUseWhy
< 300msNothing at allA spinner that flashes for 80ms reads as a glitch and makes a fast app feel broken.
0.3 – 1sInline — the control shows it is busyThe user is still looking at the thing they clicked.
1 – 5sSkeleton shaped like the contentHolds the layout so nothing jumps when data lands.
> 5sDeterminate progress, with a numberPast five seconds people need to know it is finite.
UnknownIndeterminate bar at the topNever block the screen for a wait you cannot measure.
Why the skeletons here do not shimmer.

The sweeping-gradient shimmer everyone ships is a gradient (forbidden by this language), it moves for decoration rather than information, and it draws the eye to the emptiest part of the screen. A calm opacity pulse says the same thing, quietly.

See the difference

<Delayed> · <SkeletonTable> · <Busy>

Run the same fetch three ways. The skeleton is the only one where nothing moves when the data arrives.

The set

spinner — three sizes, never alone on a page

inline-loader — busy, in place

Recalculating bottleneck cost…

skeleton-text — the last line is short, like real prose

skeleton — text, title, circle, pill, block

loading-bar — indeterminate, never blocking

progress-ring — determinate, pair with a number

68% 68

step-loader — the honest one: it names the stage

  1. Read 1,204 rows
  2. Validating columns
  3. Import
  4. !Notify owners

page-loader — first paint only

kwapso

optimistic-row — pending, and failed

Supplier intakeSaving…
Contract renewal
Not saved.

button loading — reserves its own width, so nothing jumps

States

All of them follow one rule: say what happened, then offer the way out. Note that “nothing here yet” and “your filter matched nothing” are different screens — the first is an invitation and carries the primary action, the second is a correction and carries a way back.

first-run — an invitation

No processes yet

When you map your first process, it will show up here.

no-results — a correction, echoing the query

Nothing matches “invocing”

Try a shorter search, or remove a filter.

error-state — inline failure

That didn't work

Something went wrong on our side. Trying again usually fixes it.

success-state

Your process map is ready

We found three bottlenecks worth costing.

offline-banner — watches the connection

You're offline. Changes will sync when the connection returns.

error-boundary — never a blank white screen

This part of the page stopped working

The rest of the app is still fine.

Full-page 404 / 500 / 403 / maintenance live on the screens page.

Reduced motion is not an afterthought here.

Every indicator above degrades to a still, legible form under prefers-reduced-motion — the spinner keeps its accent arc, skeletons hold at 75% opacity, the bar fills. Someone who cannot tolerate motion must still be able to tell that something is loading.