How to design loading states that match the wait
Spinners, skeletons and progress bars each suit a different wait. Here is how to choose the right loading state by duration, and how to design it well.

Most loading states get designed last, usually by dropping the same spinner onto every screen. That spinner then has to cover a 300-millisecond search, a five-second dashboard and a two-minute video export, three situations that have almost nothing in common. Someone waiting two minutes needs different information from someone waiting half a second.
The useful question isn't "which loader looks best?" It's "how long is this wait, and what does the person need to know while it lasts?" Answer that and the choice of pattern mostly makes itself. Below is the way we'd work through it on a real product, from measuring the waits to writing the words that sit next to the progress bar.
Start with the length of the wait
Before you draw anything, list every place in the product where someone waits, and measure how long each one really takes. Use real numbers from a normal connection, not what it feels like on your office Wi-Fi. Then sort them against the time limits Jakob Nielsen has been writing about since the 1990s, summarised in his guide to progress indicators:
- Around 0.1 seconds: feels instant. No feedback beyond the interaction itself.
- Up to about 1 second: noticed, but the person keeps their train of thought. Show no loader at all.
- About 2 to 10 seconds: a simple indicator is enough to show the system is working.
- Over 10 seconds: attention starts to wander. Show real progress, ideally as a percent-done bar.
- Over about a minute: assume they have left. Let them go, and tell them when the work is finished.
Treat these bands as a starting point, not a law. A checkout that takes four seconds feels longer than a report that takes four seconds, because more is at stake. But the bands give your team a shared vocabulary, and they stop the default of one spinner everywhere.
Show nothing when the wait is short
The most common mistake is showing a loader too early. If a response usually arrives in 400 milliseconds, a spinner that appears at once only flashes on the screen and disappears. That flicker makes the interface feel busier and slower than it is.
The fix is a small delay. Don't render the indicator until about a second has passed, so fast responses never trigger it. For small, low-risk actions, go one step further and update the interface immediately while the request runs in the background. Liking a post, ticking a task or renaming a file are all good candidates for this optimistic approach. Keep it away from payments and deletions, and always design the failure case: if the request fails, the interface has to undo the change and say so clearly.
Use spinners for short, unknown waits
A spinner says one thing: the system is working. It can't say how much is left. That's fine for a wait of a few seconds, and it becomes a problem past that, because a spinner looks the same at second three as at second thirty. People can't tell a slow task from a frozen one.
When you use one, place it where the result will appear. A spinner inside the button someone just pressed connects cause and effect far better than one floating in the middle of the page. Keep the button's width fixed when its label is swapped out, so the layout doesn't jump. Also disable repeat presses, or you'll get duplicate submissions from impatient users.
Be careful with skeleton screens
Skeleton screens became the fashionable answer to loading, but the evidence is less tidy than the trend suggests. Nielsen's guide points to studies that disagree: one found skeletons gave the shortest perceived load times, another found people rated them the slowest. His recommendation is to keep them for familiar, content-shaped loads of about three seconds or less.
In practice, skeletons work when three things are true:
- The placeholder matches the layout that actually arrives, so content settles into place without shifting.
- The page has a predictable structure, such as a feed, a list or a profile. A skeleton for an unknown layout is only decoration.
- Whatever is already available is shown at once. Page chrome, titles and cached data shouldn't hide behind grey blocks.
A skeleton that doesn't match the final layout is worse than a plain spinner. It promises one structure, then shows another, and the page visibly rearranges itself.
Give long waits a real progress bar
Once a wait passes ten seconds, people want to know whether to stay, so give them something they can use to decide. A determinate bar does this, and people have liked them for a long time: Nielsen cites a 1985 study by Brad Myers in which 86% of participants preferred having a progress bar, even though it did not shorten the task.
The bar has to be honest, though. If it races to 90% and sits there, people learn not to trust it. Tie it to something countable, such as files processed, rows imported or steps completed. If you truly can't measure progress, name the stages instead ("Uploading", then "Processing", then "Finishing up"). Stages are a fair alternative to a percentage you'd otherwise have to invent.
Let people leave during very long tasks
For exports, renders and imports that take a minute or more, don't hold anyone hostage on a loading screen. Move the task to the background, let them carry on working, and notify them when it finishes. Pair this with three small details:
- A clear place to check status, such as a small tray or an activity page.
- A notification that links straight to the result, not to a generic home screen.
- A way to cancel, because people often start a long task and realise the settings were wrong.
Write the words, and design the failure
Loading copy does more work than it gets credit for. "Loading..." tells people nothing. "Importing 1,240 contacts" tells them the system understood the request and confirms the scale of the job. Skip the jokes and rotating quirky messages on anything people see often, because they stop being charming by the third visit.
Then design what happens when the wait ends badly. Every loading state has a sibling: the timeout. Decide how long you wait before giving up, what the message says, and whether the person's input survives a retry. Losing a filled-in form to a failed request does far more damage than any slow spinner.
Finally, check accessibility. Respect the reduced-motion setting by replacing pulsing skeletons and spinning icons with something calmer. Announce status changes to screen readers with a live region, so that a person who can't see the spinner still knows that something is happening and when it has finished.
Make it a system, not a one-off
Put your decisions in the design system so nobody has to reinvent them under deadline. A one-page rule is usually enough: which pattern applies at which duration, the delay before indicators appear, and the standard timeout and error treatment. Add each pattern as a component state alongside hover and disabled, so a screen can't be called finished until its loading and failure states are drawn.
The short version
Measure the wait, then match the pattern to it: nothing for the shortest waits, a spinner for a few seconds, a well-matched skeleton only for familiar content, a truthful progress bar for longer tasks, and background processing with a notification for anything over a minute. Speeding up the actual response is still the best improvement you can make. But some waits will always be there, and those deserve as much design attention as the screen that comes after them.


