Interface Lab
Back to knowledge
Frontend Implementation

Loading Skeleton Progress Contract

Loading states should choose skeletons, inline loading, spinners, progress bars, or stage feedback based on duration, scope, and measurability, backed by a frontend state machine for accessibility, stability, and recovery.

Design Principle

The goal of loading feedback is not to decorate waiting; it tells users whether the system responded, what region is changing, whether they can still act, whether wait time is knowable, and how recovery works after failure. Different wait scenarios require different feedback modes.

Large Theory

Loading feedback is a frontend contract for visibility of system status. Direct manipulation should feel almost immediate; navigation and query responses under about one second should not flash skeletons; multi-second full-page or list loads need to communicate final structure; tasks over about ten seconds or with unpredictable duration need explicit progress, stages, cancellation, background handling, or notification paths. Skeletons, spinners, progress bars, inline loading, and optimistic UI are chosen by duration, scope, measurability, and risk.

Small Points

  1. Skeletons fit initial full-page, table, card-list, and detail loads when they mirror the final information architecture rather than only the app frame.
  1. Inline loading fits save, refresh, create, delete, table-row state, and small-region updates; it should appear where the affected content or trigger lives.
  1. Spinners only say the system is working; they do not say how long. If a task lasts more than a few seconds and progress can be measured, use progress or stage feedback.
  1. Determinate progress fits uploads, imports, exports, batch jobs, long computations, and countable steps. Do not fake percentages when progress is unknown.
  1. Stale-while-refresh should keep old content visible with local refresh feedback instead of replacing the page with skeletons on every refetch.
  1. Streaming should progressively replace stable placeholders while keeping partial results readable; do not show empty or final success while the stream is still open.
  1. Skeleton motion must respect prefers-reduced-motion; shimmer can degrade to static placeholders, a light fade, or no animation.

Design Judgment

Choose the loading mode by answering five questions: has the user already seen stale content, will the wait exceed one second, is the affected region full-page or local, is progress truly measurable, and can the user cancel, retry, or leave safely. If those answers are unclear, define the state model and copy before adding visual effects.

Implementation Advice

Define LoadMode and LoadStatus: none, skeleton, inline, spinner, determinate, stage, optimistic, stale-refresh, streaming; idle, pending, success, error, cancelled. The model should include label, regionLabel, progress(value/max/unit), stage(current/steps), staleContentVisible, disableDuplicateAction, canCancel, canRetry, and reducedMotionFallback. Empty states must be gated separately, such as status === "success" && items.length === 0; errors render retry or recovery separately; cancellation should not masquerade as failure. Use min-height, aspect-ratio, fixed table-row skeletons, and reserved button width to reduce layout shift. In React, place skeletons near data boundaries instead of clearing the whole app shell with one global Suspense fallback.

Accessibility

Set aria-busy on the affected region; determinate progress needs an understandable label plus aria-valuenow/min/max; search, filter, and refresh completion should use a polite live region; urgent failure is the rare case for assertive or alert. Skeleton placeholders usually should not be read one by one by assistive tech. Pending buttons disable duplicate submission but keep understandable labels such as Saving, Saved, or Save failed.

Anti-patterns / Risks

A sub-second request flashes a skeleton; refresh clears stale data; upload uses an endless spinner; a button resizes when Save becomes Saving; skeletons are more dominant than real content; motion ignores reduced-motion preferences; fake progress claims 87%; failure leaves a spinner running forever; screen readers repeatedly announce Loading Loading Loading.

Case Analysis

NN/g's skeleton-screen guidance says skeletons reduce perceived wait and cognitive load by mimicking the page structure, while frame-only skeletons are not recommended because they communicate no content structure. Carbon's Loading pattern separates skeleton states, loading indicators, and progressive loading; it limits skeletons to container/data-based components and says not to represent toast, dropdown, modal, or loader elements themselves as skeletons. Carbon Inline Loading defines active, finished, and error states and disables duplicate submission. Material/Android progress guidance separates determinate and indeterminate indicators: measurable work shows completion, unknown work only indicates ongoing processing. Together, these sources show that task classification precedes visual treatment.

Sources

NN/g Skeleton Screens 101: https://www.nngroup.com/articles/skeleton-screens/

NN/g Website Response Times: https://www.nngroup.com/articles/website-response-times/

Carbon Loading Pattern: https://carbondesignsystem.com/patterns/loading-pattern/

Carbon Loading Component: https://carbondesignsystem.com/components/loading/usage/

Carbon Inline Loading: https://carbondesignsystem.com/components/inline-loading/usage/

Android / Material Progress Indicators: https://developer.android.com/develop/ui/compose/components/progress

Agent Directive

Classify operation scope, duration, and progress measurability first, then output loading mode, state machine, copy/live-region text, layout-stability rules, reduced-motion fallback, error/success/cancel handoff, and desktop/mobile QA.