Visual Stability — CLS
Layout shift sources, space reservation, fonts, embeds, transform-only motion, and bfcache interactions.
CLS measures unexpected layout movement. User-initiated shifts within 500 ms often don’t count — but ads, fonts, and late images still ruin scores.
How CLS is calculated
Each shift has:
- Impact fraction — how much of viewport moved
- Distance fraction — how far elements traveled
CLS = sum of impact × distance for unexpected shifts in the session (with session window rules for SPAs).
Good ≤ 0.1 · Poor > 0.25
Common shift sources
| Source | Fix |
|---|---|
| Images without dimensions | width/height or aspect-ratio |
| Web fonts | size-adjust fallbacks, font-display: optional |
| Ads/embeds | Reserved slot with min-height |
| Late-injected banners | Reserve space in skeleton |
| Animations | transform not top/height |
insertBefore above content | Insert below fold or reserve |
CLS Playground — reserve space or let it shift
Content below will jump when the image loads without reserved space.
This paragraph shifts when layout is unstable.
Space reservation patterns
<!-- Always -->
<img src="/photo.jpg" width="800" height="600" alt="…">
<!-- Or CSS -->
.hero-media {
aspect-ratio: 16 / 9;
width: 100%;
}
<div class="ad-slot" style="min-height: 250px;">
<!-- ad loads here -->
</div>
Transform-only animations
Properties that trigger layout (expensive + shift risk): width, height, top, left, margin.
Compositor-friendly: transform, opacity.
.panel {
transform: translateY(100%);
transition: transform 0.3s ease;
}
.panel.is-open {
transform: translateY(0);
}
Fonts & CLS
FOIT/FOUT swaps change text metrics → shift.
- Preload critical fonts
- Match fallback metrics (
size-adjust,ascent-override,descent-override) - Consider
font-display: optionalfor body text
Embeds & iframes
<iframe
src="https://…"
width="560"
height="315"
title="Video"
loading="lazy"
></iframe>
For dynamic embeds, use aspect-ratio wrapper:
.embed { aspect-ratio: 16 / 9; width: 100%; }
.embed iframe { width: 100%; height: 100%; border: 0; }
CLS + SPA navigations
Soft navigations can reset CLS session windows. Avoid injecting large blocks above existing content on route change without skeleton placeholders.
bfcache note
Back/forward cache restores pages instantly. Some APIs break bfcache eligibility (unload listeners, open WebSockets) — see Module 07. CLS is measured per full navigation session.
Checklist
- All media have explicit dimensions
- Ad/embed slots reserved
- Animations use transform/opacity
- Font fallbacks metric-matched
- RUM attribution shows which elements shifted
Next: Module 05 — rendering pipeline and layout thrashing.