Skeleton Screens vs Spinners: Designing the Wait

  • UX
  • UI Design
A glass content card glyph with glowing coral skeleton placeholder bars, representing skeleton screen loading

How you show a wait changes how long it feels. Here is when to use skeleton screens, spinners, progress bars, or optimistic UI, why skeletons usually feel faster, and how to design a loading state that respects the user.

UI/UX

Every interface makes people wait. A list has to load, a form has to save, a payment has to clear, a search has to run. We spend enormous energy shaving milliseconds off these waits, and that work matters, but there is a second discipline that gets far less attention and often moves the needle more: how you design the wait itself. A wait is not dead time. It is part of the experience, and the way you fill it changes how long it feels, whether people trust the product, and whether they stay. At my studio we treat loading states as first class screens, not afterthoughts, because the difference between a spinner and a well built skeleton is frequently the difference between a product that feels sluggish and one that feels quick, even when the numbers behind them are identical. This piece is about the main ways to show a wait, why some feel faster than others, and how to choose between them without kidding yourself or your users.

The wait is part of the product

The mistake I see most often is treating loading as an engineering problem alone. The team optimises the query, compresses the payload, adds a cache, then drops a spinner on whatever is left and calls it done. That is only half the job. Perceived performance and actual performance are separate quantities, and users only ever feel the perceived one. You can have a genuinely fast endpoint that feels slow because the wait is ambiguous, and a slower endpoint that feels responsive because the wait is well designed. Both halves are worth engineering, but the design half is the one most teams leave on the table.

There is a reason this works, and it is not a trick. When a person is waiting, their brain is doing something whether you like it or not. If you give it nothing but an ambiguous spinning shape, it fills the gap with doubt: is this broken, did my tap register, how much longer. If you give it the shape of what is coming, it does something far more pleasant: it anticipates. Anticipation feels shorter than uncertainty even when the clock says they are the same. This is the whole game. You are not just occupying the eyes, you are giving the mind a job that makes the wait feel purposeful rather than stalled.

This is also where good loading design overlaps with good interface design generally. A wait that communicates clearly is a wait that lowers doubt, and lowering doubt is a big part of keeping the interface easy on the mind. When someone does not have to wonder what is happening, they have more attention left for the thing they actually came to do. So the loading state is not a cosmetic layer bolted on at the end. It is a communication problem, and communication is design.

Spinners: honest, cheap, and often lazy

The spinner is the default, and defaults are powerful, which is exactly why it gets used in places it should not be. A spinner says one thing and one thing only: something is happening, please hold. That is a genuinely useful message, but it is a thin one. It tells you nothing about what is coming, how much of it there is, or how close you are. It is a shrug rendered in motion. For very short waits that is fine, because there is not enough time for the thinness to matter. For anything longer, the spinner starts to feel like the product is thinking hard, and a product that visibly strains does not inspire confidence.

The spinner’s real problem is that it is content free. A spin gives the eye nothing to anticipate, so the mind falls back on watching the clock, and watching the clock is the fastest way to make a wait feel long. There is a well worn rule of thumb that a response under roughly a tenth of a second feels instant, that up to about a second feels connected though not immediate, and that once you are past a few seconds attention wanders and people disengage. A spinner does nothing to fight that drift. It just spins while the seconds pile up, and every extra second under an ambiguous spinner feels heavier than the same second under something that shows progress or structure.

None of this means the spinner is bad. It is a specific tool for a specific job: short or genuinely unpredictable waits where you cannot honestly show structure or progress. The failure is not using a spinner. It is reaching for it by reflex because it is the least effort option, then leaving it running for four seconds on a screen where you could have shown the shape of the content instead. Cheap and honest is a fine combination for a small wait. Cheap and lazy is what you get when you use it everywhere.

Skeleton screens: showing the shape of what is coming

A skeleton screen replaces the ambiguous spinner with grey placeholder shapes that mirror the layout of the content about to arrive. Where the headline will be, there is a grey bar the width of a headline. Where the avatar will be, there is a grey circle. Where the paragraph will be, there are two or three grey lines of decreasing length. Usually a soft shimmer or pulse moves across these shapes to signal that the screen is alive and working rather than frozen. The user is looking at a rough pencil sketch of the page that is about to be inked in.

The reason this usually feels faster comes straight back to anticipation. Instead of staring at a spin and wondering what will appear, the user already knows the answer, because the skeleton has told them. Their eyes settle where the content will land, their brain models the page before it exists, and when the real data arrives it drops neatly into a structure the mind has already accepted. The wait is spent building expectation rather than tolerating uncertainty, and that shift is what buys you the feeling of speed. The gentle motion of the shimmer helps too, because implied forward motion reads as progress in a way that a static grey box does not.

There is a quieter benefit too. A skeleton that closely matches the finished layout makes the product feel considered before a single word of real content has loaded, and a considered looking interface is trusted more readily. This is the tendency to judge a tidy design as more capable at work in the loading state itself. The flip side is a warning: a lazy skeleton that looks nothing like the real content, or one that keeps shimmering long after the data could have arrived, breaks the illusion and reads as a gimmick. The technique earns its keep only when the sketch is honest about what the ink will look like.

Progress bars: for waits you can actually measure

A progress bar makes a promise the other approaches cannot: it tells you how far along you are and, by implication, how much longer you have to wait. That is enormously reassuring for the right kind of task. Uploading a large file, exporting a document, installing an update, processing a batch, running a long report: these are waits with a real, measurable extent, and a bar that fills in step with genuine progress turns an anxious open ended wait into a bounded one you can plan around. The user can decide whether to wait, switch tabs, or go and make tea, and giving people that agency is a kindness.

The critical word in that paragraph is genuine. A progress bar is only as good as its honesty, and the fastest way to lose trust is to fake it. Everyone has met the bar that races to ninety percent and then sits there for a minute, or the one that fills, empties, and fills again, or the one whose speed has plainly been chosen by an animation timer rather than any real signal from the work underneath. Each of these teaches the user that the bar is theatre, and once they have learned that, the bar is worse than useless, because now it actively lies to them. A determinate bar must map to something real. If you cannot measure the work, you do not have a progress bar, you have a decoration, and you should use a different pattern.

That is the honest boundary of this tool. Use a real progress bar when you can measure real progress, and it is often the best choice available because certainty beats anticipation when you can offer it. When you cannot measure progress, resist the urge to invent it. An indeterminate bar that sweeps back and forth is really just a spinner wearing a different coat, and a bar showing a percentage you made up is a straightforward lie. Never fake progress. It is the one rule here I would treat as close to absolute.

Optimistic UI: showing the result before the server agrees

Optimistic UI takes a different stance entirely: it removes the wait from view. When an action is very likely to succeed, you show the successful result immediately, before the server has confirmed anything, and reconcile quietly in the background. You tap the heart and it fills at once. You send the message and it appears in the thread straight away. You rename the file and the new name shows the instant you press enter. The network round trip still happens, but the user never sees it, because the interface has already assumed success and moved on. Done well, this is the closest thing to zero perceived latency, and it is why the best messaging and productivity apps feel so immediate.

The catch is the reconciliation, and it has to be handled with real care. You are writing a cheque against a result that has not cleared, so you need a graceful plan for when it bounces. If the server rejects the action or the network drops, you must roll the interface back to its previous state clearly and calmly, ideally with a short, plain explanation and an easy way to retry. The failure has to be rare, because a product that constantly shows success and then snatches it back is more distressing than one that made you wait in the first place. Optimistic UI is a bet, and you should only place it on actions with a very high success rate and low stakes if they fail. A like or a reorder is a fine bet. A payment confirmation or a booking is not, because there the cost of showing a false success is far too high.

Because the technique hides the wait rather than decorating it, it pairs beautifully with the others. You can be optimistic about the small, safe writes and fall back to a skeleton or a spinner for the genuine loads. The mental model I use is simple: optimistic UI is for actions the user takes, where you can predict the outcome, while skeletons and spinners are for content the user requests, where you cannot show a result you do not yet have. Keep those two jobs separate and the choice usually makes itself.

The thresholds that decide which one to reach for

Underneath all of these choices sit a few perception thresholds, and once you internalise them the decisions get much easier. The first is the instant band. Below roughly a tenth of a second, a response feels immediate, as though the interface reacted to your touch directly rather than going away to do work. If you can get an action into that band, do it, and do not put any loading indicator on it at all, because a spinner that flashes for eighty milliseconds is visual noise that makes a fast action look busy. This is the zone optimistic UI is chasing, and it is the reason keeping interactions inside the snappy response window that keeps people in flow is worth so much engineering effort. Under that threshold, people and machines converse; over it, people wait on machines.

The second band runs from about a tenth of a second up to a second or so. Here the user notices a small delay but stays connected to their action, and a brief, well chosen indicator smooths things over. The third band is the dangerous one: past a few seconds, attention starts to wander, doubt creeps in, and people begin to wonder whether anything is happening at all. This is where ambiguous spinners do the most damage and where structure earns its keep. If a wait is going to land in this band, a skeleton or an honest progress bar is close to mandatory, because you need to actively hold attention rather than passively occupy it.

A practical wrinkle sits on top of these bands: the anti flicker rule. If a wait might be very short, do not show a loading state the instant the request fires. Wait a short beat, something in the region of a couple of hundred milliseconds, before revealing the indicator. If the data arrives before that beat is up, the user sees no loading state at all and the transition feels instant. If it does not, the indicator appears and does its job. This avoids the ugly flash where a spinner or skeleton blinks on and off so quickly that it registers as a glitch rather than a signal, which makes a fast interface feel unstable. Show the wait only once the wait is real enough to be worth acknowledging.

Avoiding layout jank when the real content lands

A skeleton screen makes an implicit promise: the real content will land exactly where the sketch said it would. Break that promise and you have done real harm, because a skeleton that does not match the final layout produces the worst kind of loading experience, the one where the page visibly reflows and jumps the moment data arrives. The user has started reading, or reached to tap, and the ground shifts under them. This layout jank is more jarring than a plain spinner, because you raised an expectation of structure and then violated it. A skeleton that lies about the layout is worse than no skeleton at all.

The fix is discipline about dimensions. The skeleton and the real content must reserve the same space: same heights, same widths, same spacing, same number of lines where you can predict it. Images and media are the usual culprits, because their intrinsic size is often unknown until they load, so reserve their space explicitly with a known aspect ratio rather than letting them push everything around when they pop in. The goal is that swapping the skeleton for the real thing changes the pixels inside each box but never the position or size of the boxes themselves. If you can achieve that, the transition feels like ink filling a sketch. If you cannot, it feels like the page tripping over itself.

This connects directly to how you load in the first place. If you are deferring images and offscreen sections to speed up the initial paint, the placeholders you leave behind have to hold their ground so nothing shifts as the deferred pieces arrive. Getting deferred loading right without the page shifting under people is really the same discipline as building a good skeleton: both are about reserving space honestly so content fills in rather than barges in. Treat the reserved box as a contract. Whatever eventually lands there must fit the space you promised.

Accessibility: announce the wait to everyone

Every loading pattern discussed here is fundamentally visual, and that is a problem the moment you remember that not everyone is looking at the screen. A shimmer means nothing to a person using a screen reader. A spinner is silent to assistive technology unless you tell it to speak. If the only signal that the product is working is a visual one, then for some of your users the interface has simply gone quiet, and silence after an action reads as failure. The loading state has to be announced, not just drawn, or it does not exist for everyone.

In practice this means giving assistive technology a clear, spoken account of what is happening. When a load begins, the fact that content is loading should be announced through a live region so it is read out without stealing focus. When the content arrives, that should be conveyed too, so the user knows the wait is over and the screen is ready to be explored. For determinate progress, expose the actual value so the technology can report how far along the task is, rather than leaving a bar that is meaningful only to the eye. Purely decorative skeleton shapes, on the other hand, should be hidden from the accessibility tree entirely, because a screen reader announcing a dozen empty placeholder boxes is noise, not help. The principle is to expose the meaning of the wait and hide the mere decoration of it.

There is also a comfort dimension worth honouring. The shimmer and pulse that make skeletons feel alive are motion, and some people are sensitive to motion or have asked their system to reduce it. Respect that preference: soften or remove the animation for anyone who has requested reduced motion, and lean on a static or very gentle indication instead. A loading state should reassure every user, and reassurance that triggers discomfort is a contradiction. Designing the wait well means designing it for the people who hear it and the people who feel it, not only the people who watch it.

A prose decision guide

Here is how I actually decide, and it is less a flowchart than a short series of honest questions. Start by asking whether the wait is even worth showing. If you can plausibly get the response under about a tenth of a second, show nothing, because any indicator on a near instant action just makes it look slower than it is. If the wait might be short but is not guaranteed to be, add the anti flicker delay so the indicator appears only when the wait becomes real. Getting these two decisions right removes a surprising amount of unnecessary loading chrome before you have chosen any pattern at all.

Next, ask what kind of wait you are dealing with. If it is an action the user just took and you can reasonably predict it will succeed, and the cost of being wrong is low, be optimistic and show the result immediately, with a calm rollback ready in case the bet loses. If it is a request for content that has a shape you know in advance, use a skeleton that honestly mirrors that shape and reserves its space precisely. If it is a discrete task whose progress you can genuinely measure, use a real progress bar and never a faked one. And if the wait is very short, truly unknown in length, or a full page transition where there is no meaningful structure to preview yet, a spinner is the honest choice. The spinner is not the loser here, it is simply the tool for the case where you cannot promise structure or progress without lying.

Finally, sanity check the whole thing against how it will be remembered, because people judge an experience by its rough shape rather than by an average of every second, and the way endings and peaks dominate the memory of an experience applies to loading as much as to anything else. A smooth, structured wait that resolves cleanly into content, with no jank at the swap and no flash at the start, leaves the impression of a quick, confident product, even if the raw milliseconds were unremarkable. A wait that ends in a reflow, a fake bar snapping to full, or a false success being yanked back, poisons the memory of the whole interaction. So design the wait as carefully as you design the content, mind the moment it ends most of all, and remember that the honest options almost always age better than the clever ones. Waiting is part of the experience. Treat it like it matters, because to the person on the other side of the screen, it does.