Most loading decisions are wrong before the animation even starts.
Teams usually ask whether a surface should use a skeleton or a spinner. The better question is simpler: what exactly is unavailable right now, and what useful context can the user keep seeing while they wait?
Once you ask that, the choice gets easier.
The mistake is not picking the wrong aesthetic. The mistake is picking a loading pattern before understanding the shape of the wait.
Skeletons work best when the interface already knows what is coming.
A dashboard card grid is a good example. You know there will be three cards. You know where the chart header goes. You know the metric label sits above the value. The only missing part is the data itself.
That is the ideal skeleton case. The placeholder preserves layout, helps the page feel stable, and gives the user a truthful preview of the structure that is about to arrive.
But skeletons stop working the moment they start pretending.
If the final layout is not actually known yet, a detailed skeleton often communicates false confidence. If the data is already available from the server or the client cache, showing a skeleton anyway is just delaying truth. If the user clicks a filter and the entire table turns into fake rows for 300 milliseconds, the product feels slower, not smoother.
The practical rule is this:
Skeletons are layout placeholders, not decoration.
Spinners still have a job. It is just a smaller job than most interfaces give them.
A spinner makes sense when the user needs acknowledgement that work is in flight, but a full placeholder layout would not add clarity. That usually happens in one of two situations.
First, the system is doing something process-like rather than content-like. Exporting a CSV, connecting an account, uploading a file, or verifying a domain are all cases where the user is waiting on progress, not waiting for a page structure to fill in.
Second, the spinner can live close to the action that caused the wait. A
button that changes to Saving... with a spinner or subtle
activity cue is often better than blanking the surrounding surface. The
user clicked one thing. The pending signal should stay attached to that
one thing.
What does not work is using a spinner as a generic replacement for missing design decisions.
A full-page spinner on a settings screen usually means the interface gave up too much context. A centered spinner in an empty white area tells the user nothing about what is loading or whether the screen they were just looking at still exists. In both cases, the spinner is honest about the fact that time is passing, but not honest enough about what the user is actually waiting on.
This is the option teams skip most often.
Not every update deserves visible loading chrome. If a sort is local, there should be no spinner. If a tab switch is instant because the content is already in memory, there should be no skeleton. If a background refresh is happening while the current table is still useful, do not wipe the table just to prove that something is happening.
This is where "nothing" does not mean "no feedback ever." It means do not replace the interface with loading theater.
Sometimes the right move is:
That still counts as choosing nothing at all in the main surface, because the user is not being forced to look at a fake empty state while perfectly usable information disappears.
This matters most in admin tools, dashboards, and settings pages. Those interfaces are dense. People are reading them, comparing numbers, and checking details. Clearing everything because a background refresh started is usually worse than temporarily showing slightly stale data.
If you need a quick rule, use this order:
That maps pretty cleanly:
1 is yes, use a skeleton1 is no and 2 is yes, use a spinner3 is yes, use nothing in the main surface and preserve
the current UI
The edge cases matter, but this is good enough to fix most loading decisions immediately.
A lot of loading UI gets added because teams are nervous that users will miss something.
So they add shimmer. Then a spinner. Then a fade. Then they disable half the page. Then they hide the data that was already useful. All of that comes from a reasonable instinct, but the result is a product that feels less trustworthy.
Good loading UI does not dramatize work. It explains work.
If the user needs a stable layout preview, give them a skeleton.
If they need confirmation that a process is underway, give them a spinner near the cause.
If the UI can keep showing the current state without confusion, let it.
That is usually the fastest path to an interface that feels calmer, faster, and more honest.