design hubs
On this page
Feedback
3 min read

Loading and Progress

Loading states communicate that the system is working — the right pattern depends on how long the wait is, whether progress is measurable, and what is being loaded.

Waiting is tolerable when the user knows they are waiting. Without a loading state, a slow operation is indistinguishable from a broken one.

The three patterns

Three patterns cover most loading scenarios. A spinner communicates indefinite activity — the system is working, but duration is unknown. A progress bar communicates measurable progress — use it when you know the percentage complete or the steps remaining. A skeleton screen replaces content with its structural outline while it loads — use it for content-heavy pages where showing the layout immediately reduces perceived wait time and prevents layout shift.

Choosing between them

Use a spinner for short, indeterminate operations (under ~3 seconds) — button states, inline fetches, authentication checks. Use a progress bar for operations with a known endpoint — file uploads, multi-step processes, data migrations. Use a skeleton screen for page loads and content fetches where the layout is known in advance. Do not use a spinner for page loads — it collapses the entire page to a single rotating icon and communicates nothing about the structure being loaded.

Inline versus full-screen

Loading states should be as localised as possible. If a single section of a page is refreshing, show a spinner or skeleton for that section — not a full-page overlay. Full-page loading states are appropriate only when the entire page is being replaced or an action requires blocking the entire interface (such as a critical form submission). Blocking the full UI for a localised operation is disproportionate and prevents users from doing anything else while waiting.

Optimistic updates

For many interactions — liking a post, saving a preference, reordering a list — you can apply the change to the UI immediately and confirm with the server in the background. This pattern, called an optimistic update, eliminates the perceived wait entirely. If the server request fails, revert the change and show an error. Optimistic updates feel fast because they are: the UI responds before the network does.

Accessible loading states

Loading indicators that are only visual fail screen reader users. A spinner that is purely CSS — or an SVG with no text — announces nothing. Three requirements apply:

Announcement — when a loading state begins, screen readers should be informed. The most common approach is a visually-hidden text element inside a role="status" or role="alert" live region: <div role="status" aria-live="polite"><span class="sr-only">Loading…</span></div>. The polite politeness setting announces after the user’s current action completes; use assertive only for critical updates that warrant interruption.

Progress — when using a progress bar, the <progress> HTML element is the most accessible choice: it exposes its value to assistive technologies natively. When implementing a custom progress bar, use role="progressbar" with aria-valuenow, aria-valuemin, and aria-valuemax.

Completion — when loading completes, announce the outcome. “12 results loaded” or “Upload complete” communicated via a live region closes the feedback loop for users who cannot see the visual change. This announcement is separate from and complementary to the visual state change.

Skeleton screens have a specific accessibility consideration: if they use animated shimmer effects, those effects must respect prefers-reduced-motion. A static skeleton is preferred; if animation is used, override it to animation: none under the reduced motion preference.

The takeaway

Every action that takes longer than 100ms needs a loading state. Design it before you build the feature. Decide whether a spinner, progress bar, or skeleton is appropriate for the duration and context. Never leave a triggered action with no visible response.

Practice

0 / 3

Keyboard shortcuts

Show shortcuts
?
Search
CtrlK
Previous article
Next article
Close
Esc