Thinking

Please Don’t Let It Sit at 99%

Design Systems·April 24, 2026·2 min read

Our table loaded twenty rows behind a skeleton of three, and the whole page jumped.

When I audited the Scaler platform, I expected the filters to be a mess, and they were. Loading turned out to be worse, though. It was broken in all six modules, and nobody had ever complained about it, because each case felt too small to bother with.

In some modules the content area collapsed to nothing while data loaded, so the page jumped up and then dropped back down when the table arrived. Other modules had a skeleton, which sounds better, except a table of twenty rows would load behind a skeleton of three. It was the same jump, just with better manners.

If you’re an analyst halfway through a compliance report, that jump is exactly how you lose your place. Then you scroll around looking for the row you were on, and the product starts to feel like it’s working against you a little.

We had all the usual loading components in the library. Nobody had written down when to use which one, so people grabbed whatever was quickest to build. I can’t really blame them.

So now I start from the wait itself. If the screen has a predictable layout, like a table or a list of cards, use a skeleton that matches the real thing, with the same row count and the same column widths. For a single action, like a button after you submit, a spinner is fine, delayed by a couple of hundred milliseconds so it doesn’t flash when the response is quick. Anything long and measurable, like an export or an upload, gets a progress bar.

The four loading state patterns, and when each belongs
PatternDurationBest for
Spinner2–10 secondsSingle actions, button states after submission: bounded wait with no layout to preview
Skeleton screen1–10 secondsContent loads with predictable structure: cards, lists, grids, data tables
Progress bar10+ secondsMeasurable operations: uploads, exports, batch processing, multi-stage flows
Optimistic UI< 300ms (assumed)Low-stakes reversible actions: likes, toggles, task completion, status changes
A spinner communicates activity. A skeleton communicates structure. A progress bar communicates measurable progress. Optimistic UI removes the wait entirely. Each solves a different problem.

Please don’t let that bar sit at 99%. Everyone has watched a progress bar do that, and nobody has fully trusted one since.

Sometimes the best loading state is none at all. If you like a post or tick off a task, just show it as done and deal with the rare failure. I wouldn’t do that for payments or deleting things, though. When money or someone’s data is involved, a second of visible “working on it” is reassuring, and people actually want it.

The four patterns: what each looks like in use
Spinner
Saving changes…
2–10s · single action · no layout to preview
Skeleton screen
1–10s · known layout · content load
Progress bar
report_q4.csv
73% · 2.1 MB of 2.9 MB
10s+ · measurable operation
Optimistic UI
★Saved to favouritesUndo
<300ms · reversible action
Start from the condition, not the component. Duration, content type, and action stakes determine the pattern, not which component is in the design system.

The rule we wrote at Scaler was boring: the layout never collapses while something loads. It fixed more than any new component would have.

References

  1. Nielsen Norman Group (2024). Skeleton Screens 101. nngroup.com
  2. Nielsen Norman Group. Skeleton Screens vs. Progress Bars vs. Spinners (Video). nngroup.com
The best loading state is the one nobody notices. ⏳ The best loading state is the one nobody notices. ⏳ The best loading state is the one nobody notices. ⏳ The best loading state is the one nobody notices. ⏳