Part V β The Craft Β· Lesson 10.3
Shimmer UI
In one lineShow the shape of the content while it loads β perceived speed improves even though nothing got faster.
Where you've seen itThe grey card outlines on a video grid, filling in one by one.
Diagram
SAME 1.8s WAIT, DIFFERENT EXPERIENCE
SPINNER
0ms βββββββββββββββ 1800ms βββββββββββΊ
[ β³ spinning ][ content ]
"is this broken?" sudden jump, layout shifts
SKELETON
0ms βββββββββββββββ 1800ms βββββββββββΊ
[ β€β€β€ shaped placeholders ][ content ]
"it's coming, and it'll content lands in the SAME boxes
look like that" β no layout shift (CLS = 0)THE SKELETON MIRRORS THE REAL LAYOUT
loading loaded
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
β β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€β€ β β [ thumbnail image ] β
β β β β
β β€β€β€β€β€β€β€β€β€β€β€β€β€β€ β β How the Web Works β
β β€β€β€β€β€β€β€ β β 42K views Β· 2 days ago β
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
same dimensions, same positions β this is the whole pointWHICH LOADER, WHEN
< 300ms nothing a flash of loader is worse than none
300msβ1s spinner too short for a skeleton to help
> 1s, known SKELETON layout is predictable
layout
> 1s, unknown progress bar uploads, exports, long jobs
duration
background inline indicator never block the whole screenHow it works
- Mirror the real layout exactly β same dimensions, spacing, and count. A skeleton that doesn't match reintroduces the layout shift it was meant to prevent.
- Delay by ~200β300ms so fast responses never flash a loader.
- Animate subtly β a slow shimmer sweep signals activity; anything fast or high-contrast is distracting.
- Respect
prefers-reduced-motionand drop the animation to a static tint. - Hide it from screen readers (
aria-hidden) and announce the state change via a live region instead (8.3).
.skeleton {
background: linear-gradient(90deg, var(--s2) 25%, var(--s3) 50%, var(--s2) 75%);
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
.skeleton { animation: none; background: var(--s2); }
}Trade-offs
| Skeleton | Spinner |
|---|---|
| Sets expectations, no layout shift | Trivial to add anywhere |
| Must be maintained alongside layout | Says nothing about what's coming |
| Best for predictable content | Fine for short or unknown waits |
Better than both, where possible: render cached data immediately and revalidate behind it (6.9). The fastest loading state is the one the user never sees.
Interview angle
"Spinner or skeleton?"
- Skeleton when the layout is known and the wait exceeds a second β it removes the layout shift and sets expectations.
- Spinner for short or unpredictable waits; nothing at all under ~300ms.
- Best of all is stale-while-revalidate, so returning users see content instantly and it refreshes underneath.
Recap
- Skeletons buy perceived speed and protect CLS by reserving exact space.
- Delay the loader ~300ms; never flash it.
- Reduced motion and screen-reader handling are part of the component, not extras.