Skeleton vs spinner
A skeleton draws the shape of content that hasn't arrived yet. A spinner reports that something is happening, with no shape at all. Ask whether you know the layout in advance, and how long the wait runs.
The rule
Both fill the gap between a request going out and an answer coming back. A skeleton draws the shape of what's coming, so the layout lands before the data does. A spinner reports motion and describes nothing, which is why it fits anywhere. One question settles most cases: do you know the layout in advance? A feed, a profile and a table render the same arrangement every time, so a skeleton can stand in for it. A button mid-submit or a search whose results could be any shape gets a spinner, because there's nothing to mimic yet.
Duration decides whether either one appears at all. Jakob Nielsen's three response-time limits, published in Usability Engineering in 1993, still hold: 0.1 second reads as instant, 1 second keeps someone's flow going, and 10 seconds is roughly where attention leaves. Under a second, the honest answer is often to show nothing.
Side by side
| Skeleton | Spinner | |
|---|---|---|
| What it communicates | The shape of what's arriving | That something is in progress |
| Layout known in advance | Required | Not required |
| Space reserved | The exact block the content will fill | None, so the page moves when data lands |
| Wait it suits | Roughly 1 to 10 seconds, a full region or page | A short or unpredictable wait, often a single control |
| Typical placement | A feed, a card grid, a table, a profile | Inside a button, beside one row, centred in a small panel |
| Build cost | One placeholder per layout, kept in step as that layout changes | One component, reused across the whole product |
| When the layout shifts | Nothing moves if the placeholder was sized right | Everything below the spinner moves once content replaces it |
The term skeleton screen comes from Luke Wroblewski, who wrote about the pattern in 2013 as an alternative to filling waits with a spinner. YouTube now shows thumbnail-shaped blocks while a page of videos loads and swaps each one in as it resolves. Stripe's dashboard keeps a small spinner inside the submit button during a payment and saves the full skeleton layout for pages loading a genuinely unknown amount of data, like a transactions table.
Use a skeleton when
- You know the layout that's coming, because it renders the same way every time
- The wait runs past about a second, long enough that a shape has time to be read
- The area is big enough that a blank region would look broken
- Reserving the space matters, so nothing below jumps once the data lands
Use a spinner when
- The result's shape can't be predicted ahead of the response
- The waiting element is a single control, like a button submitting a form
- The area is too small to hold a placeholder that would read as anything
- One part of an already-loaded screen is refreshing, like a single table row
Where people get it wrong
The one you see most is a skeleton on a request that finishes in 150ms. It flashes and vanishes, which reads as a rendering glitch. A common practice is holding the loading UI back by 200 to 300ms so fast responses never trigger one, and that single delay removes most flashing across a product.
Close behind is a skeleton whose proportions miss the real content. Three identical grey bars stand in for a heading, a paragraph and a date, then the real block arrives half again as tall and shoves the page down. That's the layout shift the placeholder existed to prevent, and it's measurable: Google's Core Web Vitals treat a Cumulative Layout Shift of 0.1 or less as good at the 75th percentile, and a mismatched skeleton is a reliable way to miss it.
The reverse mistake is a full-page spinner on a screen whose layout has been the same for two years. The data takes exactly as long either way, and the version that shows a shape while it waits feels faster to the person watching it, because they can start reading structure before content exists.
Then there's the state neither component handles: failure. A spinner that never resolves and a skeleton that pulses forever look identical to a product that has silently broken. Both need to hand off to an error message the moment a request fails, and to an empty state when the request succeeds with nothing in it.
The layout question still decides it. If you can describe the shape of the answer before it arrives, a skeleton can draw it. If you can't, a spinner is the honest option, and it costs a fraction of the work.
Last reviewed 30 JUL 2026