01 — Foundation

Loading states reduce uncertainty

A spinner is not a UX strategy — it is brief feedback while something completes.

Loading states tell users something is happening, whether the interface is still responsive, and whether waiting is expected. Bad loading UI creates confusion, repeated clicking, and distrust.

This pattern defines practical expectations for accessible, predictable loading behaviour — real usability, not animated waiting-room theatre.

02 — Principles

Inform, do not replace content

Users came for content and actions — not loading choreography.

  • explain what is loading and whether interaction is blocked
  • show whether progress exists and when action succeeded or failed
  • use the smallest feedback that fits the task
  • prefer inline feedback so users keep context and orientation

03 — Scale

Match feedback to the task

Not every action needs a fullscreen overlay.

Small actions

  • toggles, filters, likes — subtle state change or disabled control
  • button label changes like “Saving…” beat a mystery spinner alone

Large actions

  • uploads, exports, payments — progress, status text, and blocking only when necessary
  • long-running tasks: say whether users can leave and how completion is signalled
<button type="submit" disabled aria-busy="true">
    Saving…
</button>

04 — Progress

Progress and skeletons

Visible progress helps when waiting becomes noticeable.

  • determinate progress when you can measure it — upload percentage, steps
  • indeterminate spinners only with context — not endless mystery rotation
  • skeleton screens when layout stability matters — not for tiny updates
  • reserve space to avoid layout shift while content loads
<label for="upload">Invoice PDF</label>

<input id="upload" type="file" aria-describedby="upload-status">

<p id="upload-status" role="status" aria-live="polite">
    Uploading… 45%
</p>

<progress max="100" value="45">45%</progress>

05 — Accessibility

Accessible state communication

Loading is accessibility — especially for screen readers and motion sensitivity.

  • announce meaningful changes with role="status" oraria-live="polite" — not endless “loading” spam
  • disable controls during submission to prevent duplicate actions — and say why
  • respect prefers-reduced-motion for spinners and shimmer effects

06 — States

Empty, error, and save states

Loading, empty, failure, and success are different states — keep them distinct.

Do not confuse “no results”, “still loading”, and “request failed”. Users should understand which state they are in immediately.

Too vague

'Something went wrong'

Clearer

'Upload failed. Check your connection and try again.'

Save and autosave need visible state too — saving, saved, failed. Silent autosave without feedback creates anxiety.

07 — Overlays

Blocking overlays and performance

Fullscreen blocking should be rare — faster systems beat better spinners.

  • block the full UI only when interaction must pause or inconsistency would hurt
  • deliver useful content early — do not hide everything behind hydration
  • the best loading optimisation is often less JavaScript and faster responses

Anti-patterns to avoid

  • endless spinner with no context or progress
  • fullscreen overlay for a small filter change
  • skeleton screens on every minor update
  • loading UI with no disabled actions — users click again and again

08 — Review

Before you approve

A short checklist for loading states in code review.

  • feedback is proportional and reduces uncertainty
  • buttons and controls communicate state clearly
  • progress visible where waiting is noticeable
  • announcements meaningful; motion restrained; layout stable
  • error recovery and save states are understandable

When in doubt, ask: Does this help users feel informed and confident, or merely prove the frontend is busy? If the answer is “busy”, improve the system.