DEV Community

Cover image for CLS Deep-Dive: Common Causes and Fixes for Layout Shift
Apogee Watcher
Apogee Watcher

Posted on • Originally published at apogeewatcher.com

CLS Deep-Dive: Common Causes and Fixes for Layout Shift

Cumulative Layout Shift (CLS) is the Core Web Vital that measures visual stability: whether the page jumps under the user while they are trying to read or tap. A bad CLS score rarely means “CSS is slow” in the abstract. It usually means something changed size or position after the browser first painted, without the user doing something that should move the layout.

Google’s own guidance defines CLS as a score that combines how much unstable content shifts and how far it moves, with good experiences targeting 0.1 or lower at the 75th percentile of page loads in the field (Cumulative Layout Shift (CLS), Optimize CLS). What matters is that you are optimising for real navigations, not a single Lighthouse run.

We go deeper here than the CLS section inside LCP, INP, CLS: What Each Core Web Vital Means and How to Fix It. That piece sets thresholds and a short fix list. This article unpacks causes you see on real client builds, how to show which one fired, and what to standardise so the same bug does not reopen on the next template change.

If you need the wider CWV picture first, read What Are Core Web Vitals? A Practical Guide for 2026.

What CLS is measuring (and what it ignores)

CLS aggregates unexpected layout shifts: movement that was not clearly tied to a user gesture in the same interaction window. Images that load without reserved height, fonts that swap metrics, consent banners that push the hero down, and carousels that resize the track all show up here.

It does not penalise every animation. Transforms and opacity changes that do not trigger layout sit outside the problem class we are fixing. The painful shifts are almost always layout-affecting: width, height, margin, padding, or DOM insertions that reflow siblings.

Thresholds for field and lab alignment

Rating Score
Good ≤ 0.1
Needs improvement above 0.1 up to 0.25
Poor above 0.25

Lab CLS from Lighthouse or PageSpeed Insights can read higher or lower than CrUX on the same URL. Use lab traces to see which elements moved; use CrUX (where available) to see whether Chrome users actually experienced instability on navigations you care about.

Why lab CLS and field CLS diverge

Lighthouse-style lab runs focus heavily on initial load. Field CLS can include shifts that happen seconds later: personalised modules, consent flows, chat widgets, and infinite scroll batches. Google’s CLS documentation calls out that gap explicitly when comparing lab tooling with real-user data (Optimize CLS). If your CrUX line is red but Lighthouse is green, do not assume the tooling is wrong. Assume your post-load behaviour is shifting the layout.

Session windows and “bursts” of movement

CLS in the field uses session windows: clusters of layout shifts that occur close together in time count as a single worst burst rather than punishing every micro-jitter twice. The metric evolved to better match how people perceive instability (Evolving the CLS metric). You do not need to memorise the algorithm to ship fixes, but you should know why a single giant banner insertion can dominate the score for a visit even if the rest of the page is calm.

The six causes we see most often on production sites

1. Images, video posters, and iframes without reserved space

The classic failure mode: a hero or card image has no width and height (or no stable aspect-ratio), so when bytes arrive the box grows and everything below it jumps.

Fix: set intrinsic dimensions on the element, or lock an aspect-ratio in CSS that matches the asset. For responsive sets, pair width and height with CSS that scales (max-width: 100%; height: auto is a common pattern when the attributes match the true aspect ratio). For embeds, reserve the iframe box up front.

2. Web fonts that change glyph metrics after swap

When a fallback font and the web font differ in line height or glyph width, text blocks reflow the moment the custom font activates. That is a layout shift even if the user only sees “the text twitch”.

Fix: subset fonts, reduce weight variants you do not need, prefer font-display: optional when a flash of wrong metrics is worse than a short wait, and use size-adjust and fallback tuning where your stack supports it. Preload only the few files that above-the-fold text truly needs.

3. Ad slots, recommendations, and third-party embeds

Empty div placeholders that expand when the network returns an ad size, or “smart” widgets that inject height late, are a major CLS source on media and e-commerce templates.

Fix: give the slot a minimum height that matches your default creative size, negotiate fixed sizes with your ad stack where possible, and avoid collapsing the slot to zero before fill. For non-ad embeds, treat them like images: reserve space and fail closed to a fixed-height error state.

4. Client-side inserted UI above the fold

Cookie banners, promo ribbons, A/B test headers, and “install our app” bars often mount after first paint. If they push main content down, CLS goes up.

Fix: render the shell (or a non-interactive placeholder with the same height) in the initial HTML, or keep the banner in an overlay that does not reflow the document flow. Legal and marketing need wireframes that show reserved space, not only mock-ups at final load.

5. Dynamic content lists and infinite scroll

New cards appended above existing content (for example “new since your last visit”) shift everything the user is reading. Even append-below patterns can shift if heights were guessed wrong.

Fix: prefer append-below with stable card heights, skeleton rows that match final layout, and content-visibility only where you understand its effect on measurement. If you must prepend, animate within a reserved strip rather than reflowing the whole column.

6. Hydration and client-rendered headers in SPAs

Server HTML shows a compact header; the hydrated app mounts a taller nav or adds icons, so the first paint and the “settled” UI disagree.

Fix: match server and client markup for above-the-fold chrome, defer non-critical chrome below the fold, and split shell CSS so the first paint already includes the final header height.

How to prove which cause fired

Work in this order so you do not optimise the wrong layer:

  1. Reproduce in lab. Run PageSpeed Insights on mobile and desktop for the URL and template you care about. Open the “avoid large layout shifts” style diagnostics and note the DOM nodes Lighthouse names.
  2. Use Chrome DevTools. Performance recording with the Layout Shift track, or the Experience section in the Layout pane, ties shifts to frames. The Layout Shift Regions overlay makes jumps obvious on a screen recording. Chrome’s performance docs describe layout shift culprits as a structured way to name unstable elements (Layout shift culprits).
  3. Compare with field. If CrUX shows CLS pain but lab is clean, suspect logged-in routes, personalised headers, slow devices, or interaction-heavy pages where shifts happen after idle. Lab is still useful for element lists; field tells you whether the pain is widespread.

For third-party-heavy pages, cross-check with Third-Party Scripts and Performance: How to Identify and Fix the Worst Offenders. The worst CLS offenders are often the same tags that compete with LCP for bytes and main-thread time.

Layout Instability API and PerformanceObserver

When you need telemetry in staging (or a one-off console script on a client clone), the Layout Instability API surfaces individual shift events: impacted nodes, scores, and timing. Pair it with a PerformanceObserver so you can log shifts during scripted flows or synthetic monitoring. Google documents the pattern under Debug layout shifts. Chromium-only support is the trade-off, so keep PSI and CrUX as your cross-browser story.

Fix order that matches how agencies ship work

When time is limited, we use this stack rank on client sites:

  1. Intrinsic sizing for all above-the-fold media and embeds. One pass through the template often drops CLS more than any single JS tweak.
  2. Font strategy for the first screen. Lock text blocks that marketing edits often (headlines, promos).
  3. Consent and promo bars. Move to overlay or reserve height in the base layout.
  4. Ad and recommendation slots. Minimum heights and explicit collapse rules.
  5. Framework hydration mismatches. Pair with whoever owns the design system.

CLS, conversion, and why stakeholders should care

Layout shift is not only a Core Web Vital checkbox. It shows up as mis-clicks on primary buttons, lost reading position on long articles, and distrust when the UI feels like it is fighting the user. We do not quote a single universal “CLS costs X percent revenue” figure here because those studies vary by vertical and methodology. We do treat checkout, lead forms, and sticky CTAs as high-risk surfaces: if the button moves between pointer-down and click, you lose conversions even when the metric still looks acceptable on a marketing homepage.

That is why we prefer template-level budgets (PDP, PLP, article, checkout) over one site-wide number. A homepage can absorb a small consent-banner shift; a three-field payment flow cannot.

Stopping CLS from coming back

CLS regressions are layout regression tests. Treat them like accessibility or SEO regressions: add checks rather than hoping QA spots a one-pixel jump.

Your team keeps the design system; we need stable URLs under test and thresholds that match what your stakeholders agreed. CLS is a good candidate for a hard ceiling (for example, lab CLS below 0.1 on checkout and PDP templates) because shifts there map directly to mis-taps and abandoned reads.

Does fixing CLS help SEO?

Google treats Core Web Vitals as part of the wider page experience signal set; CLS is one leg. Fixing CLS alone does not replace good content or links, but severe instability can correlate with poor engagement metrics that search systems already react to. Treat CLS as user trust and conversion hygiene, not a magic ranking switch.

Why does my Lighthouse CLS differ from Search Console?

Different populations and sessions. Lighthouse uses a controlled load; CrUX aggregates real Chrome navigations. Personalisation, auth, slower hardware, and different viewport sizes change which elements move. Use both: lab for cause lists, field for whether the pain is widespread.

Should I use transform for every animation?

transform and opacity are safe for many micro-interactions because they often skip layout when used alone. They do not fix a missing image box or a late-inserted banner. Use transforms for motion; use dimensions and slot contracts for stability.


CLS is one of the few vitals you can improve with mostly HTML and CSS discipline. Start by sizing what the user sees first, then tighten fonts and third parties, then add checks to your release path so the next small marketing change does not undo the work.

When you are ready to monitor CLS and the rest on a schedule across sites, open pricing and pick a plan that matches your portfolio.

Further reading

Top comments (0)