Six loading states that make your app feel fast
Perceived performance is a design problem, not a benchmark. Six loading patterns — skeleton shimmer, orbital spinners, liquid loaders and more — with when to use each.
7 min read
Speed is a feeling
Two apps can take the same 800ms to load and feel completely different. The difference is what the user sees during the wait: a frozen white screen reads as "broken", while a shimmering skeleton reads as "working". Perceived performance is the cheapest performance win you will ever ship.
The six patterns, and when to reach for each
- Skeleton shimmer — for content-shaped waits (feeds, cards, dashboards). The layout is already visible, so nothing jumps when data lands.
- Orbital spinner — for sub-second actions (saving, sending). Small, inline, never fullscreen.
- Liquid / blob loaders — for brand moments: onboarding, uploads with personality. One per app, not one per page.
- Determinate progress — for anything over ~3 seconds. A moving bar beats any spinner because it promises an end.
- Optimistic UI — the best loader is none: render the success state immediately and roll back on failure.
- Staggered reveal — when the data arrives, don't flash it all at once; cascade the rows in over 200ms.
The one rule that separates pros from amateurs
Never let a loader appear for less than ~300ms. A spinner that flashes for 80ms reads as a glitch. Delay the loader: only show it if the wait exceeds 300ms, and fade it in — this single detail is the difference between "feels instant" and "feels janky".
Second rule: keep loading states honest. If you fake a 500ms skeleton for a 50ms fetch, you made the app slower to feel faster. Skeletons are for real waits.
Steal these, don't design them
Loading states are pure choreography — timing curves and opacity. That makes them perfect prompt material: describe the wait, not the pixels.
✦SVG Draw CheckboxesThe same stroke-draw idea applied to success states — pairs beautifully with loaders.