My learning 0 of 20 lessons complete
Start lesson 1 →
Back to compare picker

Loading spinner vs Loading skeleton vs Infinite spinner vs Error state

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.

Learn the decision

Match the interaction to the person's task

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.

Loading spinner

Use it when: The operation has defined resolution, timeout, cancellation, or failure paths.

Make this state clear: Inline processing state for a button or small local action.

Observe the result: After submit, users see payment PAY-2048 processing, can tell the button is temporarily unavailable, and then get either success or retry guidance.

Study the full pattern →

Loading skeleton

Use it when: The content shape is predictable.

Make this state clear: Initial skeleton placeholder state with region marked busy.

Observe the result: Users see that reports are pending, then can switch the demo to loaded, empty, or error outcomes on a bounded path.

Study the full pattern →

Infinite spinner

Use it when: Use this anti-pattern entry to audit loading, saving, syncing, uploading, report generation, billing retrieval, AI generation, and import flows that can hang.

Make this state clear: Initial pending state that names the operation and affected object.

Observe the result: The user can wait briefly, see that billing data is delayed, open cached values, retry once, or escalate with a reference.

Study the full pattern →

Error state

Use it when: A system or task failure blocks expected content or action.

Make this state clear: Normal expected state before failure.

Observe the result: User input and filter context are preserved after failure, and retry returns to recovered content or a clear still-failed state.

Study the full pattern →

Practice the judgment: Imagine this situation: A Save button shows a spinner but leaves the original button clickable, creating duplicate submissions. Write the person's goal, choose one of these patterns, and show the state that helps the person continue. Use the decision rules and source trail below to explain your choice.

Review the underlying principle →

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.Keep skeleton dimensions stable, mark the affected region busy, and use subtle or reduced-motion-respecting animation only while loading is unresolved.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 Prevent duplicate or conflicting actions while the operation is in flight, but keep context, status, safe cancellation, and timeout recovery available when the wait exceeds the expected threshold.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.Match the next action to the failure cause: retry transient failures, edit correctable input, use cached fallback for read-only continuity, or provide support reference when recovery is outside the UI.
Good UI A Pay invoice button becomes Processing payment with a small spinner, disables only duplicate payment actions, and leaves the invoice reference visible.The interface shows a clear action and state.Billing data is loading shows the affected account, elapsed wait, and actions for Retry, Use cached values, and Contact support after timeout.A failed save state keeps the draft visible and labels the support reference separately from the user-facing explanation.
Bad UI A search results panel shows skeleton rows, a spinner, and a progress bar for the same request.Skeleton rows look like clickable report cards and receive focus before content exists.A Save button turns into a spinner forever, leaving the user unable to cancel, retry, or know whether the save happened.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.Large layout shift after content arrives.A failed backend response is hidden behind a permanent spinner.Clearing work after save failure.
Best fit The operation has defined resolution, timeout, cancellation, or failure paths.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.A specific progress indicator or blocking message is required.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 Inline processing state for a button or small local action.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.Give progress indicators accessible names that identify the operation or affected region.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
Prevent duplicate or conflicting actions while the operation is in flight, but keep context, status, safe cancellation, and timeout recovery available when the wait exceeds the expected threshold.
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 search results panel shows skeleton rows, a spinner, and a progress bar for the same request.
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
The operation has defined resolution, timeout, cancellation, or failure paths.
Avoid when
The content layout is predictable and a skeleton would better preserve structure.
Required state
Inline processing state for a button or small local action.
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
Keep skeleton dimensions stable, mark the affected region busy, and use subtle or reduced-motion-respecting animation only while loading is unresolved.
UX guidance
Reduce uncertainty and layout shift while predictable content loads, then resolve clearly to real content, empty state, or error state.
Good UI
The interface shows a clear action and state.
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
Large layout shift after content arrives.
Best fit
The content shape is predictable.
Avoid when
A specific progress indicator or blocking message is required.
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 Save button turns into a spinner forever, leaving the user unable to cancel, retry, or know whether the save happened.
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
A failed backend response is hidden behind a permanent spinner.
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
Give progress indicators accessible names that identify the operation or affected region.
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
Match the next action to the failure cause: retry transient failures, edit correctable input, use cached fallback for read-only continuity, or provide support reference when recovery is outside the UI.
Good UI
A failed save state keeps the draft visible and labels the support reference separately from the user-facing explanation.
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.

Saved items