Skeleton Loading
Placeholder shapes matching the layout of content that is still loading, shown instead of a spinner so the page keeps its structure.
Skeleton loading shows placeholder shapes matching the layout of the content that is still arriving. Instead of an animation reporting that something is happening, the page renders the structure of what is coming, in grey.
The difference from a spinner is what the person can do during the wait. A spinner offers nothing but the wait. A skeleton lets somebody see the shape of the screen, read whatever is already known, and decide where they are going to look when the data lands.
Why it works
The layout does not jump. Content arriving into an empty container resizes everything around it. A skeleton reserves the space, so the arrival is a fill rather than a reflow. This is the largest practical benefit and the one people notice without being able to name.
The wait feels shorter. Not because it is, but because the time is spent orienting instead of watching. The measured duration is identical and the experience is not.
Structure carries information. Seeing that a screen has four metric cards, a chart and a table tells somebody something before any number arrives.
Where a skeleton is the wrong choice
When the shape is unknown. A search that might return one result or fifty cannot be skeletonised honestly. Guessing produces the jump the technique exists to prevent.
When the wait is very short. Under roughly 200 milliseconds, show nothing. A skeleton that flashes is the same flicker a spinner produces.
When the wait is very long. Past about ten seconds a skeleton has nothing more to say. That case needs real progress or permission to leave and be notified.
When the page is genuinely empty. A skeleton followed by an empty state is a promise of content that never arrives, which is worse than showing the empty state directly.
When it becomes decoration. A screen of shimmering rectangles across every region at once is visual noise. Skeletonise the region that is loading, not the entire interface.
Getting it right
Match the real layout. Same number of rows where that is knowable, same proportions, same spacing. A skeleton that does not resemble the result is a spinner that took longer to build.
Use the real container. The skeleton should live inside the component that will hold the content, so the transition is a swap rather than a replacement.
Keep the shimmer slow and quiet. Its job is to say "this is loading, not empty". It should respect prefers-reduced-motion, and the reduced version should still be distinguishable from real content.
Do not skeletonise what you already know. Navigation, headings, tabs and the page title are usually available immediately. Render them and skeletonise only the pending part.
Announce it. Movement without text tells a screen-reader user nothing. The loading state needs a live region or an accessible label, the same requirement any loading indicator carries.
In B2B interfaces
Tables are the natural case. Column headers are known before the data, so render the header and skeleton the rows. This is the single highest-value application in most enterprise products.
Row counts are not known. Skeleton a plausible number, then let the real count settle. This is one place where a small jump is acceptable, because the alternative is showing nothing.
Independent regions load independently. A dashboard whose widgets each fetch separately should skeleton each widget rather than the page, so the fast ones appear as soon as they can.
Permission-dependent regions are a trap. Skeletonising a section a person cannot see promises content that will never arrive. Resolve permissions before deciding what to render.
Skeletons and perceived performance
The claim usually made for skeletons is that they make a page feel faster. That is worth stating precisely, because the imprecise version leads teams to use them as a substitute for being fast.
A skeleton does not change the duration. What it changes is what the wait is spent on. Watching an animation gives somebody nothing to do and no way to judge progress; seeing the structure of the page lets them read headings, locate the region they care about and prepare to read.
Two consequences follow. A skeleton improves a wait that is already reasonable and cannot rescue one that is not, so past roughly ten seconds it stops helping. And it is not a performance optimisation, which matters when a team starts reporting improved perceived performance to people who assumed the page got quicker.
In practice
A table replaces its whole area with a centred spinner while rows load, which takes around two seconds against real data.
Switching to a skeleton changes three things. The column headers render immediately, so somebody can see what the table contains. Eight placeholder rows hold the height, so the page below does not move when the data lands. And the toolbar above stays interactive, so filters can be set while the rows are still coming.
The request takes exactly as long as it did. What changed is that the two seconds are now usable, and the arrival is no longer a jolt.
Where teams get it wrong
- A skeleton that does not match the result. The layout jumps anyway.
- Skeletonising the whole page. Including the parts already known.
- Fast or high-contrast shimmer. Unpleasant to look at, and the person is looking at it.
- A skeleton before an empty state. A promise of content that never arrives.
- Ignoring reduce-motion. A screen of pulsing shapes for somebody who cannot tolerate it.
- No announcement. Silent to assistive technology, like any purely visual indicator.
Frequently Asked Questions
Related terms