Cumulative Layout Shift: Finding the Causes Systematically
AI generated
60fps
ms
Performance · Core Web Vitals · CLS
Finding CLS Causes Systematically
Diagnosing the four most common Cumulative Layout Shift triggers instead of guessing

Cumulative Layout Shift is one of the three Core Web Vitals and measures how much visible elements on a page unexpectedly change position while a visitor is already interacting with or reading the page. A high CLS score leads to mis-clicks, lost reading position, and a general impression of instability, even when every other performance metric looks good. The four most common causes, images without dimensions, late-loading ad banners, web font swap, and content dynamically inserted above existing content, can be tracked down systematically with the Layout Instability API and the browser DevTools, instead of stumbling onto them by accident during testing.

13 min read CLS · Core Web Vitals Layout Instability API

1. What CLS measures and why visitors notice it

Cumulative Layout Shift is calculated from two factors, the impact fraction, meaning the share of the viewport affected by a shift, and the distance fraction, meaning the distance elements moved relative to the viewport size. Both values get multiplied for every individual, unexpected layout shift and summed across the entire lifetime of the page, with Google defining a total score of 0.1 or below as the target for a good user experience.

The reason CLS carries so much user relevance despite its technical calculation formula lies in the immediate, often frustrating experience a layout shift triggers: a click lands on the wrong button because it moved down at the last moment, or reading position in an article gets lost because an ad banner suddenly pushes the text downward. This experience is independent of how fast the page objectively loaded, which is exactly why Google treats CLS as its own metric on equal footing with LCP and INP.

2. Cause one: images without dimension attributes

By far the most common CLS cause is img elements without explicitly set width and height attributes or a matching aspect-ratio in CSS. Without these, the browser reserves no space for the image on first render, since it only learns the actual size once the image file has fully downloaded and decoded. Once the image arrives, everything below it abruptly jumps down to make room for the now-known space the image needs.

The fix is technically simple but still frequently overlooked in practice, especially with image lists loaded dynamically from a CMS or API where the original image dimensions are not consistently delivered alongside the image itself. The example below shows both the classic solution via width and height, whose ratio the browser automatically interprets as an aspect ratio, and an explicit CSS approach for cases where dimensions are only known at runtime.


<!-- Classic solution: width/height automatically reserve the space -->
<img
  src="/images/blog-article-header.avif"
  width="1200"
  height="630"
  alt="Article header"
  loading="lazy"
/>

<!-- CSS approach for dynamically loaded images with unknown dimensions -->
<style>
  .product-image-container {
    aspect-ratio: 4 / 3;
    background: var(--image-placeholder-color, #e5e5e5);
  }
  .product-image-container img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
</style>

3. Cause two: late-loading ad banners without reserved space

Ad banners inserted only after the initial page build by an asynchronously loaded ad script are among the most persistent CLS causes, since their final size often only becomes known after the actual ad has loaded, and the surrounding content has no reserved space for it until then. Ad placements above the main content, for instance right below the header, are particularly problematic, since a shift occurring there affects practically the entire visible viewport at once and therefore produces a high impact fraction value.

The most reliable countermeasure is reserving a fixed container with a minimum height matching the most common or largest expected ad size for every ad slot, even if the actual ad turns out smaller and temporarily leaves empty space. That deliberately accepted empty space is generally the lesser evil compared to a noticeable layout shift, since empty but stable space barely registers with visitors, while an abrupt jump of the entire page content stands out immediately.

4. Cause three: web font swap and FOUT

Web font swap happens when a page first renders with a system font and only swaps in the actual web font once its file has downloaded, a behavior controlled through the font-display property in CSS. With font-display: swap, the fallback font renders immediately and gets swapped later, which improves perceived text load time but can trigger a layout shift if the fallback font and the web font have different character widths, changing line breaks and the overall height of text blocks during the swap.

The most effective countermeasure is choosing a fallback font whose metrics, meaning character width, x-height, and line spacing, come as close as possible to the actual web font, instead of using an arbitrary system font. Tools that automatically generate metric-adjusted fallback fonts compute size-adjust, ascent-override, and descent-override values for @font-face that align the fallback font with the target font closely enough that the swap causes little to no layout shift at all.

5. Cause four: content dynamically inserted above existing content

Content dynamically inserted above already-present content is the fourth major CLS cause and typically occurs when a consent banner library, a notification, or a late-loading widget gets inserted above the already-visible main content instead of appearing as an overlay on top of it. Since the new content claims the existing space, everything below it shifts downward, often at the exact moment the visitor has already started reading.

The fundamental difference from the previous three causes is that this problem usually is not caused by missing size reservation, but by a fundamentally wrong positioning strategy: elements that appear temporarily or asynchronously and should not permanently claim space in the content flow belong outside the normal document flow, positioned as position: fixed or position: absolute, so their appearance overlays the surrounding content visually instead of shifting it.

6. Debugging with the Layout Instability API

The Layout Instability API exposes every individual layout shift event at runtime through the PerformanceObserver, including the affected elements, the exact distance and impact fraction values, and a timestamp. This lets you trace exactly which element shifted, when, and by how much, per user session, which is significantly more precise for debugging than a single aggregate score that only shows the sum of all shifts without indicating which individual element contributes the most.

In production this observer can be enabled permanently and its data sent to a monitoring system, so CLS regressions after a deployment do not surface only through manual spot checks but get automatically reported together with the concrete, causing element. It matters to only include shifts without recent user input, per the API's hadRecentInput flag, in the CLS calculation, since layout changes the visitor themselves triggered, say by expanding an accordion, do not count as a negative experience.

7. Visually identifying layout-shift regions in DevTools

Beyond programmatic capture through the Layout Instability API, Chrome DevTools' performance panel offers a visual representation of every individual layout shift as a color-marked region on the rendering timeline, showing exactly which part of the page was affected at which point in time. Clicking the corresponding layout shift event in the timeline additionally highlights the concrete DOM element in the elements panel, often narrowing the cause down to a single, specific element within seconds instead of manually searching through the entire page structure.

In addition, the Lighthouse report's diagnostics section explicitly lists the elements with the largest contribution to the total CLS score, sorted by their respective share, which helps prioritize the fix order by actual impact rather than subjective guessing on pages with multiple simultaneous causes. For recurring regression testing, the same Lighthouse report can also be wired into a CI pipeline to catch new CLS contributors before deployment.

8. Weighing layout shifts by impact fraction and distance fraction

Not every measured layout shift deserves the same attention, since the impact fraction and distance fraction formula produces very different contributions to the total score for different causes. A small icon shifting by a few pixels produces a negligible contribution, while an ad banner pushing the entire viewport down by several hundred pixels can dominate a page's CLS score on its own. Prioritizing fixes should therefore consistently follow the actual, measured contribution of each individual element, not the perceived importance of the affected area.

In practice this means using the contribution-sorted list from the Layout Instability API or Lighthouse as a direct work list, addressing the two or three elements with the highest share first before even looking at smaller shifts. This approach generally gets a page under the 0.1 target score noticeably faster than fixing causes in whatever order they happen to be discovered in the codebase.

9. Building prevention into the development process

The most sustainable fix for CLS is preventing the four common causes structurally during development, instead of debugging them after the fact in production. A linting rule flagging img elements without width and height attributes, a mandatory standard for reserved ad slot containers, and a project-wide standard for metric-adjusted fallback fonts already noticeably reduce the number of newly introduced CLS problems before code review even happens.

In addition, a CLS budget should be established as a fixed part of the CI pipeline, failing a build once a Lighthouse run on core page types exceeds a defined threshold. This combination of preventive rules during development and automated checks before every deployment reliably keeps CLS regressions from going live at all, instead of discovering them only afterward through Real User Monitoring.

Cause Typical symptom Main diagnostic tool Fix approach
Images without dimensions Content jumps down as the image loads Lighthouse diagnostics, DevTools elements panel Consistently set width/height or aspect-ratio
Late-loading ad banners Large jump above the main content Layout Instability API, performance panel Reserve a fixed minimum height for ad containers
Web font swap Line breaks shift during the font swap DevTools rendering timeline Use a metric-adjusted fallback font
Dynamic content above existing content Text shifts while the visitor is reading PerformanceObserver with hadRecentInput filter Position elements as an overlay instead of in the document flow

Mironsoft

Web performance, Core Web Vitals, and load time optimization

Load times that don't make users bounce before the page is even visible?

We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.

Performance Audit

Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.

Bundle Optimization

Specifically reducing JavaScript and CSS bundle size and improving code splitting.

Monitoring Setup

Establishing continuous performance monitoring instead of a one-time snapshot.

10. Summary

Finding CLS Causes: The Key Points

Core idea

CLS almost always comes from one of four recurring causes that can be tracked down systematically rather than by chance.

Most important tool

The Layout Instability API delivers the concrete element, timing, and exact contribution to the total score per event.

Prioritization

Sort by impact fraction and distance fraction, and fix the two or three biggest contributors first.

Prevention

Linting rules and a CLS budget in the CI pipeline stop new regressions before they ever reach deployment.

11. FAQ: Finding CLS Causes: The Key Points

1What is a good CLS target score?
Google defines 0.1 or below as a good user experience. Scores between 0.1 and 0.25 need improvement, and anything above that counts as poor.
2Do all layout shifts count toward the CLS score?
No, shifts occurring within 500 milliseconds of a user interaction are excluded from the calculation through the Layout Instability API's hadRecentInput flag.
3Is width and height on the img tag enough to prevent CLS from images?
In most cases yes, since the browser automatically computes an aspect ratio from them and reserves space before loading. For responsive images with a variable aspect ratio, additional CSS aspect-ratio helps.
4Why does font-display: swap cause a layout shift?
Because the fallback font and the actual web font usually have different character widths, which can change line breaks and the overall height of text blocks during the swap.
5How do I reserve space for ad banners with a variable size?
By giving the container a fixed minimum height matching the most common or largest expected ad size, even if smaller ads leave temporary empty space.
6How do I find the element with the largest CLS contribution?
Through the contribution-sorted list in Lighthouse's diagnostics section, or through the value property of each event from the Layout Instability API.
7Are layout shifts caused by user interaction a problem?
No, shifts that directly result from a user action like expanding an accordion are excluded from the CLS calculation and do not count as a negative experience.
8Can a consent banner cause CLS?
Yes, if it is inserted above the main content within the document flow instead of as an overlay. Positioned as position: fixed or position: absolute, it does not shift the surrounding content.
9How can I catch CLS regressions before deployment?
Through a CLS budget in the CI pipeline that fails a build once an automated Lighthouse run exceeds a defined threshold.
10What is the difference between impact fraction and distance fraction?
Impact fraction measures the share of the viewport affected by the shift. Distance fraction measures the distance of the shift relative to the viewport size. Both get multiplied to calculate the contribution of a single shift.