CLS Debugger Example: 0.18 Score Root Causes Found
This example shows the aiwebpageseo CLS Debugger for a news article page with CLS 0.18 — above the 0.1 threshold. Three shift sources are identified: a sticky ad unit loading late (contributes 0.09), a web font swap causing text reflow (0.06), and a product recommendation widget injected without reserved space (0.03).
Sticky ad unit — div#sticky-ad-bottom — loads after content
A 90px sticky ad banner loads 1.2 seconds after the main content, pushing all page content up by 90px. Fix: Reserve space for the ad with min-height: 90px on the container before the ad loads. Use a placeholder div with the correct dimensions.
Web font swap — "Merriweather" swapping from Georgia fallback
Merriweather loads 800ms after the fallback font (Georgia) is rendered, causing text reflow as character widths differ. Fix: Add font-display: optional to the @font-face declaration and use size-adjust: 105% to match Georgia metrics. Add <link rel="preload"> for the font file.
Product recommendation widget — injected without reserved space
A "You might also like" widget is dynamically injected at the bottom of the article 2.4 seconds after load without reserved space. Fix: Use CSS min-height or aspect-ratio on the container div to reserve space before the widget loads.
The three shifts sum exactly to the reported score, and that is not a coincidence — it is telling you something specific about when they happened.
Since June 2021, CLS is not the total of every shift on the page. It is the score of the single worst session window: a burst of shifts where each occurs within 1 second of the last, with the whole window capped at 5 seconds. Shifts in separate windows do not add together — only the worst window counts.
So when three shifts sum neatly to the reported figure, they fell inside one window. Here that is unmistakable in the timings: the ad at 1.2s, the font at 0.8s, the widget at 2.4s — a cascade of instability across the first few seconds, all landing in the same burst.
What this means for the fix order — and it is counter-intuitive
Because only the worst window counts, fixing the 0.09 ad does not necessarily drop you to 0.09. If removing it also breaks up the burst — so the font swap and the widget now fall into separate windows — the score could drop to 0.06. Conversely, fixing only the 0.03 widget may change the score by nothing at all, if the remaining two still form the worst window. Always re-measure after each fix rather than assuming the numbers subtract.
Both are late-loading elements without reserved space. The 3× difference in damage comes from the formula, and understanding it lets you predict which shifts will hurt before you measure them.
- Impact fraction — how much of the viewport was affected by the moving content.
- Distance fraction — how far it travelled, relative to the viewport's largest dimension.
The two multiply. The sticky ad shifts the entire article — every visible element on screen — by 90px. Enormous impact fraction. The recommendation widget sits at the bottom of the article, below almost everything, so it displaces very little of what is actually in view.
The lesson generalises: position determines cost. An element injected at the top of a page has the maximum possible impact fraction, because everything moves. The identical element placed lower down scores a fraction of the same penalty. Where you put a late-loading component is a CLS decision, not just a design one — and it explains why cookie banners and top-of-page ad slots are the most destructive things you can add to a page.
The report's fix — min-height: 90px on the container — is correct in principle. Two things then go wrong in practice, and both re-introduce the shift you thought you had fixed:
Ad networks serve variable sizes into one slot
Reserve 90px, and a 250px unit lands: you have shifted by 160px and gained nothing. Reserve the height of the size that is served most often, not the smallest. If the slot genuinely serves several sizes, reserve for the common case and accept the occasional shift, or constrain the slot to a single size in the ad configuration.
Never collapse an unfilled slot
When no ad is returned — which happens on a meaningful share of loads — a container that collapses to zero pulls everything up. An upward shift scores exactly the same as a downward one. The reserved space must persist whether or not the ad arrives.
For a sticky element specifically, there is a better answer: take it out of the document flow entirely with position: fixed. A fixed element overlays the page instead of displacing it, and cannot cause a layout shift at all. If the ad is going to be sticky anyway, make it sticky from the first frame.
The report suggests font-display: optional and size-adjust: 105%. These are two different strategies and it is worth knowing which one you are choosing.
| Value | Behaviour | CLS effect |
|---|---|---|
| swap | Fallback shows at once, swaps whenever the font arrives | Guaranteed shift — text always readable |
| optional | Brief block; if the font is not ready, the fallback is used for the whole page view | No shift, ever — some users never see Merriweather |
font-display: optional is the only value that structurally cannot cause a shift, because it never swaps mid-render. The cost is real: a first-time visitor on a slow connection reads the article in Georgia. For a news site, that is usually an acceptable trade — the font is not the product.
size-adjust is the more ambitious fix, and it does something different: it makes the fallback occupy the same space as the web font, so the swap becomes invisible. You keep the swap and you lose the shift. It rarely stands alone, though — the full family of descriptors is size-adjust, ascent-override, descent-override and line-gap-override, and matching Merriweather's metrics properly usually needs more than one of them.
And on the preload: crossorigin is mandatory on a font preload, even for a font on your own domain. Fonts are always fetched in anonymous mode; omit the attribute and the browser downloads the file twice, which is slower than not preloading at all.
Three things to do after deploying, in order:
- Watch the shifts happen. Chrome DevTools → Rendering → Layout Shift Regions. Tick it, reload, and every shifting region flashes. If the ad slot no longer flashes, the reservation worked. This takes ten seconds and it is more convincing than any score.
- Throttle the network to Slow 4G. Nearly all CLS is a race between content arriving and layout being computed. On a fast connection the race is over before you can see it, and the bug appears fixed when it is merely hidden. Every one of these three shifts is a timing problem — 1.2s, 0.8s, 2.4s — and on a fast office connection they may not reproduce at all.
- Re-measure the score. Because of the session-window rule, the arithmetic will not be what you expect. Measure; do not calculate.
Then wait four weeks before judging
Field CLS is drawn from real users over a 28-day rolling window, at the 75th percentile. A fix deployed today will barely register in a week. Shipping a genuine improvement, checking after five days, seeing nothing and reverting it is one of the most common and expensive mistakes in performance work.
A load-time analysis finds load-time shifts. Several categories are invisible to it, and on a real site they are frequently the ones that matter:
- Shifts that only happen to returning visitors. A cookie banner is dismissed and gone — so the layout a returning user sees is different from the one being tested. This is the most common reason lab and field CLS disagree.
- Shifts caused by interaction. Any shift within 500ms of a click, tap or keypress is excluded from CLS, because the movement was requested. This is also why clicking around a page is a poor way to debug it — you are inside the exclusion window, and the real score is generated in the first seconds of load, before anyone has touched anything.
- Shifts from personalised or logged-in content that a crawler never sees.
- Shifts further down a long article, caused by lazy-loaded images or embeds that only load as you scroll. CLS accumulates across the entire page lifecycle, not just the initial viewport.
For those, the tool is the LayoutShift PerformanceObserver API: observe entries of type layout-shift, and each carries a sources array naming the DOM node that moved, with its previous and current rectangles. Log that from real users and you can see the shifts that only occur in the wild — which are the only ones affecting your score.
What is Cumulative Layout Shift?
CLS measures how much page content moves unexpectedly during loading. A score above 0.25 is Poor, 0.1–0.25 is Needs Improvement, and below 0.1 is Good. CLS is a Google ranking factor and directly affects user experience — unexpected shifts cause users to click the wrong elements.
What causes high CLS?
Common CLS causes: images without width and height attributes (browser cannot reserve space), ads loaded after page content (pushing content down), web fonts that swap causing text reflow, dynamically injected content (cookie banners, chat widgets, notification bars) and embeds without defined dimensions.
How do I fix CLS from web fonts?
Add font-display: optional or font-display: swap to your @font-face declarations. Use font-display: optional if you can tolerate no font swap — the fallback font is used if the web font is not cached. Use size-adjust to match the fallback font metrics closely to prevent text reflow. Use rel='preload' to load the font file earlier.
If I fix the 0.09 shift, will my score drop to 0.09?
Not necessarily. Since June 2021, CLS is the score of the single worst session window — a burst of shifts each within 1 second of the last, capped at 5 seconds — not the sum of every shift on the page. Removing one shift may break up the burst so the remaining shifts fall into separate windows, dropping the score further than expected; or it may change nothing, if the remaining shifts still form the worst window. Always re-measure after each fix rather than assuming the numbers subtract.
Why does the ad cost three times as much as the widget?
Because a shift is scored as impact fraction multiplied by distance fraction, and the two multiply. The sticky ad displaces the entire article — every element on screen — so its impact fraction is enormous. The recommendation widget sits below almost everything, so it displaces very little of what is actually in view. Position determines cost: the same element injected at the top of a page is far more damaging than one placed lower down.
What is the right way to reserve space for an ad slot?
Reserve the height of the size served most often, not the smallest — reserve 90px and a 250px unit lands, and you have shifted by 160px for nothing. Never collapse an unfilled slot either: when no ad is returned, a container collapsing to zero pulls everything up, and an upward shift scores exactly the same as a downward one. For a sticky element specifically, position: fixed is better still — it overlays the page rather than displacing it, so it cannot cause a shift at all.
Why did my CLS not improve after I deployed the fix?
Most likely because you are looking too soon. Field CLS is drawn from real users at the 75th percentile over a 28-day rolling window, so a fix deployed today will barely register within a week. Verify the fix directly instead: Chrome DevTools → Rendering → Layout Shift Regions, with the network throttled to Slow 4G, since almost all CLS is a race between content arriving and layout being computed, and on a fast connection the bug may not reproduce at all.
Related Demo Reports
Run CLS Debugger on Your Own Site
Get your real audit report with specific issues, fixes and actionable improvements — free to start.