Loading States
Last updated:
Practical patterns for proportional loading feedback, progress, accessibility announcements, and states users can trust.
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-motionfor 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.