Back to blog
Ai News

Skeleton Screens vs Spinners

6 min read

Loading states seem like a minor UI detail until you realize they're one of the few moments where you have direct control over how fast your product feels — independent of how fast it actually is. Two patterns dominate: the spinner and the skeleton screen. They are not interchangeable, and picking the wrong one for the situation costs you more than you'd expect.

The core finding

Multiple UX studies converge on a consistent result: users perceive pages using skeleton screens as noticeably faster than identical pages using spinners, even when the actual measured load time is exactly the same. Estimates from this research put the perceived speed gain at around 20-30% — a meaningful gap given that nothing about the underlying performance changed at all.

Why the brain reacts differently

A spinner communicates one thing: "wait, with no information about what's coming." That's an open-ended wait state, and open-ended waits feel longer than waits with a visible endpoint — this is a well-documented pattern in queueing psychology generally, not unique to software.

A skeleton screen does something different. It shows the shape of the content before the content arrives — gray blocks where a headline, avatar, or paragraph will render. The brain interprets this as structure and starts predicting what's coming. By the time real content populates the page, the brain has already done a chunk of the processing work, which makes the transition feel closer to seamless than a spinner's abrupt "blank, then everything at once" pattern.

When to use which

This isn't a case of skeleton screens being strictly better — each pattern fits a different kind of wait:

Use a spinner when:

  • The operation is momentary and doesn't have a predictable visual shape (e.g., a button click submitting a form — replacing the label with a small spinner confirms the click registered).
  • You genuinely don't know what layout is coming, so there's nothing meaningful to sketch.

Use a skeleton screen when:

  • You're loading content into a known layout — a list of cards, a profile page, a table of rows. If you can predict the shape of what's coming, you can show it before the data arrives.
  • The wait is long enough that perceived speed actually matters (sub-200ms loads don't need either — anything you flash that briefly just adds visual noise).

Practical implementation notes

A skeleton screen only works if it roughly matches the real layout. A generic gray rectangle that doesn't resemble the eventual content breaks the illusion and can feel worse than a spinner, because the "prediction" the brain built turns out to be wrong. Match block sizes and positions to your actual components as closely as practical.

Also worth watching: don't let skeleton screens linger too long. If your data takes several seconds to load, a static skeleton that doesn't progress can start to feel like its own kind of stall. Subtle shimmer/pulse animation on the skeleton blocks helps signal "still working" without reverting to spinner-style ambiguity.

A third pattern that beats both: optimistic UI

For a specific class of interaction, there's a pattern that outperforms both spinners and skeleton screens by avoiding a visible loading state entirely: optimistic UI. Rather than showing any loading indicator, optimistic UI updates the interface immediately after a user action, assuming the request will succeed, and only corrects course if an error actually comes back from the server. For frequent, low-risk, reversible actions — liking a post, bookmarking an item, toggling a setting, marking a task complete — this eliminates perceived wait time entirely rather than just making the wait feel shorter, since there's no wait state shown to the user at all.

The boundary that matters here is risk and reversibility, not just latency. Optimistic UI is well-suited to low-stakes, easily-reversible actions, but it's the wrong pattern for irreversible or high-stakes actions — submitting a payment, deleting data, sending an email — where showing a false success state before the server has actually confirmed the action creates a worse failure mode than a brief wait would have. For those higher-stakes actions, a skeleton screen or spinner that accurately reflects "this hasn't completed yet" is doing real work by setting correct expectations, even though it's slower-feeling than an optimistic update. The practical decision tree, combined with the skeleton-vs-spinner framework above: optimistic UI for frequent low-risk toggles, skeleton screens for loading known-shape content into view, and spinners for the genuinely shapeless, momentary, or high-stakes waits where an honest "not done yet" signal matters more than perceived speed.

How modern frameworks make skeleton screens closer to free

The implementation cost of skeleton screens has dropped significantly with how current React and Next.js apps handle server rendering, which is worth knowing if "building custom skeleton components" sounds like more engineering effort than it now actually is. React Suspense boundaries and Next.js App Router's loading.tsx file convention are the two standard places to render a skeleton loader in a 2026 React app — a loading.tsx file automatically shows an instant loading UI while a route segment streams in from the server, without hand-wiring the loading state logic yourself. This pairs with streaming server-side rendering, where the server flushes HTML for fast-rendering components (navigation, layout shell, static text) immediately down the connection while slower data-dependent sections stream in afterward, each wrapped in its own Suspense boundary showing a skeleton fallback specific to that section.

A related technical detail worth building into any skeleton implementation regardless of framework: the skeleton's dimensions need to match the final content's dimensions as closely as possible, specifically to avoid layout shift (a Core Web Vitals concern in its own right, covered in more depth elsewhere) when real content finally replaces the skeleton blocks — a skeleton sized differently from its eventual content causes a visible jump that undermines the same "seamless transition" effect skeleton screens are meant to create in the first place. Streaming SSR also enables selective hydration, where each Suspense boundary becomes its own hydration unit and React can prioritize hydrating whatever section the user is actively interacting with rather than hydrating the whole page in one blocking pass — a performance benefit that comes essentially free once a skeleton-and-Suspense architecture is already in place for the loading-state reasons described above.

The bottom line

The companies that default to skeleton screens for content loading — Facebook, LinkedIn, YouTube among them — didn't arrive there by accident. Bounce rates drop measurably when spinner-based loading gets replaced with skeleton screens, not because anything got objectively faster, but because the anxiety of an undefined wait goes away. For any list, feed, or card-based UI in your product, a skeleton screen matched to the real layout is close to a free win. Reserve spinners for the truly momentary, shape-less waits where they still do their job well.

Sources: logrocket.com, onething.design, thehangline.com, v2.remix.run, uxpatterns.dev, nextjs.org, react.dev

Keep reading

Get new posts as they publish

No spam — just the next post, straight to your inbox.

Discussion