Back to compare picker

Loading spinner vs Loading skeleton vs Infinite spinner vs Error state

Choose loading spinner when a specific action or region is actively processing, progress cannot be measured yet, and the wait is expected to resolve quickly enough that a compact busy indicator is sufficient.

Decision dimensions

Dimension Loading spinnerLoading skeletonInfinite spinnerError state
UI or UX UI + UX - Bounded indeterminate wait indicator for a named action or regionUI + UX - Bounded content loading placeholderUI + UX - Unbounded loading anti-patternUI + UX - Recoverable failure surface
UI guidance Render a compact spinner only beside, inside, or over the affected action, component, or page region, and pair it with concise text that names what is loading or processing.Render neutral skeleton blocks that reserve the final content structure without fake readable text, fake controls, or focusable placeholder elements.Replace an unlabeled endless spinner with a bounded loading surface that names the operation, preserves the affected context, and changes state after the wait threshold.Render a persistent error region near the affected content with a specific failure heading, plain-language cause, preserved context, and recovery actions.
UX guidance Use a loading spinner for short indeterminate waits where the system is actively working but cannot yet expose progress; resolve it quickly to content, success, cancellation, progress, or error.Reduce uncertainty and layout shift while predictable content loads, then resolve clearly to real content, empty state, or error state.Help users decide whether to wait, retry, leave safely, use stale data, or escalate instead of forcing them to infer system health from animation alone.Help users recover when expected loading, saving, validation, sync, permission, or computation fails without losing their work.
Good UI A Pay invoice button becomes Processing payment with a small spinner, disables only duplicate payment actions, and leaves the invoice reference visible.Three report-card placeholders match the final card heights and are replaced by real cards without shifting the panel.Billing data is loading shows the affected account, elapsed wait, and actions for Retry, Use cached values, and Contact support after timeout.Reports could not load appears in the report section with the saved filter, Retry, Use cached data, and Contact support actions.
Bad UI A blank page shows a large spinner with no text, no affected object, and no idea what is loading.Skeleton rows look like clickable report cards and receive focus before content exists.A centered spinner animates on a blank page for minutes with no label or escape.Tiny transient toast for a blocking failure.
Good UX After submit, users see payment PAY-2048 processing, can tell the button is temporarily unavailable, and then get either success or retry guidance.Users see that reports are pending, then can switch the demo to loaded, empty, or error outcomes on a bounded path.The user can wait briefly, see that billing data is delayed, open cached values, retry once, or escalate with a reference.User input and filter context are preserved after failure, and retry returns to recovered content or a clear still-failed state.
Bad UX The spinner blocks the whole workspace for a small table refresh and prevents users from continuing other work.Skeleton never resolves.Users refresh the page because the spinner never explains whether the request is still working or broken.Clearing work after save failure.
Best fit A short action, request, save, submit, refresh, sync, or fetch is actively processing and progress cannot be meaningfully measured.The content shape is predictable.Use this anti-pattern entry to audit loading, saving, syncing, uploading, report generation, billing retrieval, AI generation, and import flows that can hang.A system or task failure blocks expected content or action.
Avoid when The content layout is predictable and a skeleton would better preserve structure.The system cannot predict the content layout.A short spinner is clearly labeled, tied to a specific action, and guaranteed to transition quickly.Nothing exists yet and the state is expected.
Required state Idle state with no spinner and the action or region ready.Initial skeleton placeholder state with region marked busy.Initial pending state that names the operation and affected object.Normal expected state before failure.
Accessibility burden Give the spinner or affected region an accessible name that identifies the operation.Mark the loading region busy or provide concise status text.Do not leave a region or page in a permanent busy state after loading has stalled or failed.Use appropriate alert or status semantics for newly appearing critical errors.
Common misuse Showing an unlabeled spinner on a blank page.Showing skeletons forever.Animating a full-page spinner forever after a request times out.Using a transient toast for critical errors.

Loading spinner

UI or UX
UI + UX - Bounded indeterminate wait indicator for a named action or region
UI guidance
Render a compact spinner only beside, inside, or over the affected action, component, or page region, and pair it with concise text that names what is loading or processing.
UX guidance
Use a loading spinner for short indeterminate waits where the system is actively working but cannot yet expose progress; resolve it quickly to content, success, cancellation, progress, or error.
Good UI
A Pay invoice button becomes Processing payment with a small spinner, disables only duplicate payment actions, and leaves the invoice reference visible.
Bad UI
A blank page shows a large spinner with no text, no affected object, and no idea what is loading.
Good UX
After submit, users see payment PAY-2048 processing, can tell the button is temporarily unavailable, and then get either success or retry guidance.
Bad UX
The spinner blocks the whole workspace for a small table refresh and prevents users from continuing other work.
Best fit
A short action, request, save, submit, refresh, sync, or fetch is actively processing and progress cannot be meaningfully measured.
Avoid when
The content layout is predictable and a skeleton would better preserve structure.
Required state
Idle state with no spinner and the action or region ready.
Accessibility burden
Give the spinner or affected region an accessible name that identifies the operation.
Common misuse
Showing an unlabeled spinner on a blank page.

Loading skeleton

UI or UX
UI + UX - Bounded content loading placeholder
UI guidance
Render neutral skeleton blocks that reserve the final content structure without fake readable text, fake controls, or focusable placeholder elements.
UX guidance
Reduce uncertainty and layout shift while predictable content loads, then resolve clearly to real content, empty state, or error state.
Good UI
Three report-card placeholders match the final card heights and are replaced by real cards without shifting the panel.
Bad UI
Skeleton rows look like clickable report cards and receive focus before content exists.
Good UX
Users see that reports are pending, then can switch the demo to loaded, empty, or error outcomes on a bounded path.
Bad UX
Skeleton never resolves.
Best fit
The content shape is predictable.
Avoid when
The system cannot predict the content layout.
Required state
Initial skeleton placeholder state with region marked busy.
Accessibility burden
Mark the loading region busy or provide concise status text.
Common misuse
Showing skeletons forever.

Infinite spinner

UI or UX
UI + UX - Unbounded loading anti-pattern
UI guidance
Replace an unlabeled endless spinner with a bounded loading surface that names the operation, preserves the affected context, and changes state after the wait threshold.
UX guidance
Help users decide whether to wait, retry, leave safely, use stale data, or escalate instead of forcing them to infer system health from animation alone.
Good UI
Billing data is loading shows the affected account, elapsed wait, and actions for Retry, Use cached values, and Contact support after timeout.
Bad UI
A centered spinner animates on a blank page for minutes with no label or escape.
Good UX
The user can wait briefly, see that billing data is delayed, open cached values, retry once, or escalate with a reference.
Bad UX
Users refresh the page because the spinner never explains whether the request is still working or broken.
Best fit
Use this anti-pattern entry to audit loading, saving, syncing, uploading, report generation, billing retrieval, AI generation, and import flows that can hang.
Avoid when
A short spinner is clearly labeled, tied to a specific action, and guaranteed to transition quickly.
Required state
Initial pending state that names the operation and affected object.
Accessibility burden
Do not leave a region or page in a permanent busy state after loading has stalled or failed.
Common misuse
Animating a full-page spinner forever after a request times out.

Error state

UI or UX
UI + UX - Recoverable failure surface
UI guidance
Render a persistent error region near the affected content with a specific failure heading, plain-language cause, preserved context, and recovery actions.
UX guidance
Help users recover when expected loading, saving, validation, sync, permission, or computation fails without losing their work.
Good UI
Reports could not load appears in the report section with the saved filter, Retry, Use cached data, and Contact support actions.
Bad UI
Tiny transient toast for a blocking failure.
Good UX
User input and filter context are preserved after failure, and retry returns to recovered content or a clear still-failed state.
Bad UX
Clearing work after save failure.
Best fit
A system or task failure blocks expected content or action.
Avoid when
Nothing exists yet and the state is expected.
Required state
Normal expected state before failure.
Accessibility burden
Use appropriate alert or status semantics for newly appearing critical errors.
Common misuse
Using a transient toast for critical errors.
Decision rules
  • Choose loading spinner when a specific action or region is actively processing, progress cannot be measured yet, and the wait is expected to resolve quickly enough that a compact busy indicator is sufficient.
  • Choose loading skeleton when the pending result has a predictable visual structure and users benefit from reserved card, row, chart, or text layout while content loads.
  • Flag infinite spinner when the spinner can keep running with no named operation, timeout, elapsed status, retry, cancel, cached fallback, background handoff, or failure state.
  • Choose error state when the load, save, sync, submit, or fetch has failed, timed out, lost authorization, or reached a retry limit that needs recovery.
  • Use determinate progress outside this comparison when the system can expose percent, item count, queue position, or ordered stages instead of an unspecified wait.
  • Use a small inline spinner inside or next to the triggering control for button-level waits, and disable only the controls that would duplicate or corrupt the in-flight action.
  • Use a component or page overlay spinner only when the affected surface is temporarily unavailable; keep a label nearby that names the exact operation and affected object.
  • Do not show spinner and skeleton for the same region; if both appear, they must describe separate operations with separate resolution paths.
Inspect live examples
Failure modes
  • A Save button shows a spinner but leaves the original button clickable, creating duplicate submissions.
  • A whole page is covered by an unlabeled spinner for a small filter refresh that should only busy the results region.
  • A report list shows both skeleton rows and a centered spinner, so users cannot tell which progress signal owns the wait.
  • A spinner continues after the request has timed out instead of switching to retry or error recovery.
  • A measurable upload uses an indeterminate spinner instead of progress that shows percent, file count, or completion stage.
  • A spinner appears with no accessible name, leaving assistive technology users without the operation context.