Fixing Cumulative Layout Shift
Everyone has lived this moment: you're reading an article on your phone, you reach to tap a link, and at exactly that instant an ad loads above the text, everything lurches downward, and your thumb lands on the ad. Nobody involved is happy — not you, not the site, arguably not even the advertiser billed for a click made in anger.
That lurch is layout shift, and Cumulative Layout Shift (CLS) is Google's attempt to put a number on it. Among the Core Web Vitals it's the odd one out: it isn't about speed at all, it's about stability — whether the page keeps its promises about where things are. It's also, happily, the most fixable of the three. CLS problems are almost never infrastructure problems; they're markup and template problems, which means they're solvable with an editor and an afternoon rather than a hosting migration.
How the score actually works
CLS is a unitless score, not a time. Each unexpected shift is scored as roughly how much of the viewport was affected times how far things moved, and the page's CLS is the worst burst of shifts during the visit (technically: shifts are grouped into short windows, and the worst window counts). The thresholds: 0.1 or below is good, above 0.25 is poor, at the 75th percentile of real visits.
Three properties of the scoring change how you hunt:
- Only unexpected shifts count. Movement within 500 ms of a user interaction is excused — an accordion expanding when tapped is fine. This is a design permission slip: you may move things in response to users; you may not move things under them.
- It's measured across the whole visit, not just load. A page can load rock-solid and then rack up CLS when a sticky banner appears at scroll depth, a carousel auto-rotates with sloppy sizing, or content pops in as you scroll. If your CLS is bad but load looks clean, hunt later in the session.
- Small viewports amplify scores. The same 60-pixel banner insertion eats a bigger fraction of a phone screen than a desktop monitor. This is one reason mobile CLS is usually worse, and why you should diagnose on mobile dimensions.
Finding the jumps: three techniques
CLS debugging has a special frustration: shifts are fast, intermittent, and often depend on network timing — the ad loads late only on slow connections, the font swaps only for first-time visitors with cold caches. So don't rely on eyeballing a reload. Use tools that catch shifts in the act:
- DevTools → Performance panel, with throttling. Record a page load with CPU and network throttling enabled (slow connections make shifts bigger and more visible). The recording flags every Layout Shift event; click one and Chrome highlights which element moved and from where. This is the definitive method — it names the culprit element rather than leaving you to guess.
- The Layout Shift Regions toggle. In DevTools' Rendering panel, enable Layout Shift Regions: shifted areas flash highlighted as they happen. Combine with a hard reload (cache disabled) at mobile dimensions and watch where the flashes land. Fast, visual, great for demos to colleagues who don't believe the problem exists.
- Field attribution for the shifts you can't reproduce. Lab loads are clean-room conditions: no consent state, no personalized ads, no A/B variants. If field CLS is poor but the lab looks fine, instrument real visits — the web-vitals JavaScript library reports which elements shifted for actual users, and several RUM tools expose this out of the box. Ten lines of script often solves a mystery a week of local reloading couldn't.
One more source worth checking first: PageSpeed Insights lists 'Avoid large layout shifts' with named elements in its diagnostics — frequently enough to skip the hunt entirely.
The usual suspects, and the fix for each
Decades of collective debugging have established that CLS comes from a short list of causes. Work through them in order; most sites are guilty of two or three.
1. Images without reserved space
The classic. An <img> with no dimension information occupies zero height until the file arrives, then snaps to full size, shoving everything below it. The fix is old-fashioned and delightful: put width and height attributes on every image. Modern browsers use them to compute the aspect ratio and reserve correctly-scaled space even in fluid layouts — you're not fixing the display size, just declaring the shape. For CSS-controlled boxes, aspect-ratio does the same job. CMSs typically output dimensions automatically, but hand-written HTML, some page builders, and certain 'optimization' plugins strip or omit them — a rendered-source audit of your main templates takes ten minutes and settles it.
2. Ads, and anything else injected by JavaScript
The highest-scoring shifts on the ad-supported web. An ad slot that starts at zero height and inflates when the auction resolves can shove an entire article down mid-read. The discipline: reserve the space up front. Give each slot a CSS min-height matching the most common creative size for that placement, and hold the space even if the ad fails to fill (an empty reserved box scores zero; a collapsing one causes a reverse shift). Where sizes genuinely vary, reserve for the likeliest size and accept small variance — partial reservation still removes most of the score. And never, under any circumstances, insert ads into content that's already on screen above the reader's position; if you must inject dynamically, inject below the viewport, where shifts don't count because nothing visible moves.
3. Web fonts swapping at different metrics
Custom font arrives, replaces the fallback, and every line of text reflows — a subtle, full-page shudder. Fixes in ascending effort: preload the one or two critical font files so the swap happens before first paint; self-host instead of bouncing through a third-party CSS request; and for the thorough, tune the fallback with the modern CSS descriptors (size-adjust, ascent-override) so the fallback occupies nearly identical space and the swap becomes visually free — tools exist that compute these overrides for common font pairings. Also: resist the urge to fix font shift with font-display: block; trading visible text for stability fails the reader in a different way (and hurts LCP when text is the LCP element).
4. Embeds and iframes
YouTube embeds, social posts, maps, podcast players — all iframes of initially-unknown height, all shifters. Reserve with aspect-ratio: 16 / 9 (or the embed's real shape) on the container. Social embeds are the worst offenders because their heights genuinely vary; a decent compromise is a min-height placeholder sized to the typical case, or better, a click-to-load facade — which also happens to be a large performance win, two birds.
5. Banners that push instead of float
Cookie consent bars, promo ribbons, app nudges — anything that inserts itself at the top of the document after load and pushes everything down is buying CLS at the worst possible exchange rate (full viewport width times the push distance). Fix by choosing one of two honest behaviors: overlay (position fixed/sticky, floating above content, zero shift) or pre-reserved (the layout includes the banner's space from the first paint, banner fills it when ready). The unacceptable third behavior — appear late, push everything — is the default in a dismaying number of consent tools, and most have an overlay setting one checkbox away.
6. Late-arriving dynamic content
Related-posts blocks, personalization, currency switchers, 'live' stock or price data replacing placeholders of a different size. The general principle covers all of them: whatever arrives late must land in space that was reserved early. Skeleton placeholders sized to match the final content are the standard pattern — the key word being sized to match; a skeleton of the wrong height just schedules the shift for later.
A repeatable audit, start to finish
- Pick your top three templates by traffic (article, homepage, listing — whatever they are).
- For each, record a throttled mobile load in the Performance panel and list every flagged shift with its element.
- Classify each against the suspects above — image, ad slot, font, embed, banner, dynamic block.
- Apply the matching fix at the template level, so one change repairs every page sharing the layout.
- Re-record to confirm locally, then let the 28-day field window in Search Console confirm at the 75th percentile. (Field data trails your fix; don't declare defeat in week one.)
Because fixes live in templates, CLS work compounds beautifully: five changes can repair fifty thousand URLs. It's the rare performance project with that shape.
Keeping the page still
CLS regressions arrive the same way the original problems did — a new marketing tag, a consent-tool update, an editor embedding something new, a redesign that forgets the dimension attributes. Two habits keep the score down: make 'no unreserved late content' an explicit rule for anything added above the fold (one sentence in your dev and marketing checklists), and re-run the ten-minute template audit whenever the layout or the tag manager changes. Stability is a promise the page makes to the reader's thumb — that the thing they're about to tap will still be there when the tap lands. Small promise, kept a few hundred million times a month. Worth an afternoon.
Photo: Joshua Miranda on Pexels