Skip to main content
CLS Prevention Strategies

Stop Guessing CLS: Fix Layout Shifts Without Common Mistakes

Understanding CLS: Why Guessing Fails and What Actually Causes Layout ShiftsCumulative Layout Shift (CLS) quantifies visual instability on a page. When users see content jump unexpectedly—a button they were about to click moves, or text reflows as images load—that's a layout shift. The browser measures it by comparing the positions of visible elements before and after a shift, multiplying the distance moved by the affected area. A CLS score below 0.1 is considered good, but achieving that requir

图片

Understanding CLS: Why Guessing Fails and What Actually Causes Layout Shifts

Cumulative Layout Shift (CLS) quantifies visual instability on a page. When users see content jump unexpectedly—a button they were about to click moves, or text reflows as images load—that's a layout shift. The browser measures it by comparing the positions of visible elements before and after a shift, multiplying the distance moved by the affected area. A CLS score below 0.1 is considered good, but achieving that requires more than just adding width and height attributes. Many teams fall into the trap of guessing at fixes without understanding the root cause, leading to wasted effort and unstable user experiences.

How the Browser Calculates CLS

The browser's Layout Instability API calculates a shift score for each unexpected movement. Only shifts that occur within the first 500 milliseconds of a user interaction are ignored; all others count. The score is the product of two factors: the impact fraction (the portion of the viewport that moved) and the distance fraction (how far the unstable element traveled). For example, if an ad loads and pushes the main content down by 50 pixels over 20% of the viewport, the shift score is 0.2?0.5?0.1?actually, impact fraction is 0.2 and distance fraction is 0.5, so total is 0.1. But if the ad itself shifts again, the score accumulates. This means that even small shifts can add up to a poor CLS if they happen many times.

Common Misconceptions About CLS Causes

One widespread myth is that only images without dimensions cause shifts. While that's a major factor, dynamic content—such as injected third-party widgets, lazy-loaded images with unknown sizes, or even custom fonts that cause text reflow—can be equally disruptive. Another misconception is that setting explicit width and height on all elements guarantees stability. In reality, if an element's container has no defined size, or if the element is replaced with content of different aspect ratio, the shift can still occur. For instance, a responsive image with width='100%' and height='auto' needs a parent container with a fixed height or an aspect-ratio CSS property to prevent reflow.

The Danger of Relying on Quick Audits

Many teams run a Lighthouse audit, see a red CLS score, and immediately add dimensions to all images. This can backfire: if the dimensions don't match the actual rendered size (e.g., due to CSS max-width or object-fit), the browser reserves incorrect space, causing a shift when the real image loads. A better approach is to use Chrome DevTools' Performance panel to record a page load and inspect the Layout Shift records. Each record shows exactly which elements moved and by how much. Without this diagnosis, you're flying blind.

When Layout Shifts Are Considered Acceptable

Not all shifts are bad. The API ignores shifts that result from user-initiated actions, like expanding an accordion or scrolling. Also, shifts that occur within 500ms of a tap or click are excluded. However, shifts caused by animations or transitions that are not triggered by user interaction still count. Understanding these nuances helps you prioritize fixes: if a shift is caused by a user opening a menu, you don't need to fix it; if it's caused by an auto-playing video that resizes, you do.

In summary, effective CLS optimization requires precise diagnosis rather than blanket fixes. By understanding how the browser measures shifts and what actually causes them, you can target your efforts where they matter most, avoiding the common mistakes that lead to futile guessing.

Step-by-Step Diagnosis: How to Identify the Specific Elements Causing Shifts

Before you can fix layout shifts, you need to know exactly which elements are moving and why. The browser's built-in tools offer detailed insights, but many developers overlook them or misinterpret the data. This section provides a systematic method to pinpoint the offenders, using Chrome DevTools as the primary example (similar tools exist in Firefox and Edge).

Using the Performance Panel to Record Layout Shifts

Open Chrome DevTools, go to the Performance panel, and click the Record button. Reload the page, then stop recording. Look for the Layout Shift events in the summary view—they are marked with a red triangle. Click on one to see the Details panel, which shows the cumulative score, the impacted nodes, and the shift region. The impacted nodes are the elements that moved. However, the culprit is often not the node itself but its parent or a sibling that injected content. For example, if a sidebar widget loads late, it may push down the main content; the main content appears as the impacted node, but the widget is the cause.

Identifying the True Offender with the Rendering Tab

To find the actual cause, use the Rendering tab (press Ctrl+Shift+P, type 'Rendering', and enable 'Layout Shift Regions'). This highlights unstable areas with a blue overlay. Refresh the page and watch the blue rectangles appear. Each rectangle corresponds to a shift event. Hover over a rectangle to see which DOM element triggered it. This visual feedback is invaluable for spotting third-party scripts or dynamically injected elements that you might otherwise miss.

Analyzing Shift Timing and Frequency

Not all shifts are equal. Look at the timing: if shifts occur early in page load, they may be caused by font loading or initial image rendering. If they happen later, they might be due to lazy-loaded content or user interaction. Use the Performance panel's timeline to correlate shifts with network requests or script execution. For instance, if you see a shift right after a 'DOMContentLoaded' event, it might be a script inserting an element. If shifts are recurring, consider using the 'Layout Shift' type in the console to log each shift's details programmatically.

Common Diagnosis Pitfalls to Avoid

One mistake is relying solely on Lighthouse. Lighthouse simulates a page load with a fixed viewport and network conditions, which may not reflect real user experiences. It's a good starting point, but always verify with actual user monitoring (RUM) data. Another pitfall is ignoring shifts that occur below the fold—those still contribute to CLS if the user scrolls down later. Finally, don't assume that a shift shown in DevTools is the only one; some shifts may be too small to be visible but still add up. Use the console command 'performance.getEntriesByType('layout-shift')' to get a list of all shifts with their scores.

By following this diagnostic process, you can create a prioritized list of fixes. Each shift event should be traced to its root cause—whether it's an image without dimensions, a font that causes text reflow, a third-party script that injects content, or a CSS animation that triggers a layout change. Only then should you proceed to the next step: choosing the right fix.

Reserving Space Correctly: Why Explicit Dimensions Often Backfire and What to Do Instead

The most common advice for preventing layout shifts is to always set width and height on images and videos. While this is generally correct, it's not sufficient. If the reserved space doesn't match the actual rendered size—due to responsive design, CSS constraints, or content replacement—the fix can cause shifts instead of preventing them. This section explains why naive dimension setting fails and how to reserve space effectively using aspect-ratio, container queries, and placeholder strategies.

The Problem with Fixed Dimensions in Responsive Layouts

Suppose you have an image with width='800' and height='600'. In a responsive layout, you might set max-width: 100% and height: auto. The browser initially reserves 800x600 pixels, but as the viewport shrinks, the image scales down, and the reserved height remains 600?actually, height: auto overrides the fixed height, so the browser recalculates the aspect ratio. However, if the image hasn't loaded yet, the browser doesn't know the aspect ratio, so it falls back to the intrinsic dimensions or zero. This is where shifts happen. The solution is to use the CSS property aspect-ratio on the image container, or set the image's width and height as HTML attributes and then use CSS to override only width, letting the browser compute height from the aspect ratio.

Using aspect-ratio and object-fit for Stable Reserving

The modern approach is to apply aspect-ratio: 16/9 (or the correct ratio) to the image or its container, combined with object-fit: cover or contain. This tells the browser exactly how much vertical space to reserve relative to the width, regardless of the actual image dimensions. For example, for a hero image that should be 16:9, set .hero-image { aspect-ratio: 16/9; width: 100%; height: auto; }. The browser reserves the correct height immediately, and when the image loads, it fits within the box without causing a shift. This works even if the image hasn't loaded or if it's replaced with a different-sized image.

Handling Third-Party Embedded Content

Embedded widgets—like YouTube videos, social media posts, or ad slots—are notorious for causing shifts because their dimensions are often unknown until they load. One effective technique is to wrap them in a container with a fixed aspect ratio that matches the most common embed size. For YouTube, use a container with padding-bottom: 56.25% (which is 9/16) and position the iframe absolutely inside. This reserves space proportional to the container's width, and the iframe fills it. However, if the embed is taller (e.g., a tweet with variable height), this may not work. In that case, consider deferring the embed until user interaction, or use a placeholder that matches the likely dimensions.

When to Use Placeholders vs. Skeleton Screens

Placeholders (colored boxes or blurred images) can be helpful, but they must accurately reflect the final content's size. A common mistake is to use a square placeholder for a rectangular image, which still causes a shift when the real image loads. Instead, use a placeholder with the same aspect ratio as the expected content. Skeleton screens, which mimic the page's structure with gray blocks, can also prevent shifts if the blocks are sized correctly. However, they add complexity and may not be necessary for all elements. Reserve skeleton screens for dynamic lists or cards where the number of items varies.

In summary, reserving space effectively requires more than just setting width and height. You must account for responsive scaling, unknown content dimensions, and third-party embeds. By using aspect-ratio, container-based reserving, and accurate placeholders, you can achieve stable layouts without the common pitfalls of naive dimension setting.

Font Loading and Text Reflow: The Overlooked CLS Culprit

While images and dynamic content are common shift sources, font loading can cause subtle but cumulative layout shifts that are often missed. When a web font loads, the browser may re-render text with different metrics, causing lines to reflow and elements to shift. This is especially problematic for large blocks of text or headings. The impact on CLS can be significant, yet many developers focus solely on media and ignore fonts.

How Fonts Cause Layout Shifts

When a browser first renders text, it uses a fallback font (e.g., Times New Roman) if the web font hasn't loaded. Once the web font arrives, the browser re-renders the text with the new font. If the two fonts have different metrics—line height, letter spacing, glyph width—the text block may change size, causing the layout to shift. For example, if the fallback font is wider than the web font, the text will shrink, and subsequent elements may move up. The shift score depends on how much the text block's size changes. Even a small change in line height can cause a significant shift if the text block is tall.

Using font-display to Control Rendering Behavior

The CSS property font-display controls how the browser handles font loading. The most common values are 'swap' (show fallback immediately, swap to web font when ready), 'block' (hide text for up to 3 seconds, then show web font), 'fallback' (swap with a short timeout), and 'optional' (swap only if font is cached). For CLS, 'swap' is often recommended because it avoids invisible text (FOIT). However, 'swap' can still cause shifts if the fallback and web fonts have different metrics. A better approach is to use 'optional' for non-critical fonts, which may not load on slow connections, preventing shifts altogether. For critical fonts, consider using font-display: 'fallback' with a short timeout, and ensure the fallback font is similar in metrics.

Matching Fallback Fonts to Reduce Reflow

To minimize shifts, you can use the CSS property size-adjust (part of the CSS Fonts Module Level 5) to adjust the fallback font's metrics to match the web font. This is a newer feature, but it's supported in most modern browsers. For example, if your web font is narrower than the fallback, you can set size-adjust: 95% on the @font-face of the fallback. This reduces the shift magnitude. Another technique is to use a fallback font stack that closely matches the web font's metrics, such as using 'Arial' as a fallback for 'Helvetica Neue' because they have similar widths.

Preloading Critical Fonts

Another effective strategy is to preload the web font using a tag in the HTML head. This tells the browser to fetch the font early, often before the CSS is fully parsed. Combined with font-display: 'swap', the font may load before the text is first rendered, avoiding a shift altogether. However, preloading should be used sparingly—only for fonts that are used on the initial viewport. Over-preloading can slow down other resources. Also, ensure that the font file is properly cached to avoid repeated loading.

Font-induced layout shifts are subtle but can accumulate to a poor CLS score. By understanding how fonts affect reflow, using font-display appropriately, matching fallback metrics, and preloading critical fonts, you can eliminate these hidden shifts without resorting to drastic measures like using system fonts exclusively.

Taming Third-Party Scripts and Widgets: Strategies for Unpredictable Content

Third-party scripts—ads, analytics, social media embeds, chatbots—are among the most common causes of layout shifts because their dimensions and loading behavior are outside your control. They can inject elements at any time, push content down, and cause multiple shifts. This section provides a systematic approach to containing these unpredictable elements and minimizing their impact on CLS.

Containing Third-Party Content in Reserved Slots

The first line of defense is to allocate a fixed-size container for each third-party widget. Use a div with explicit width and height, or use aspect-ratio to reserve space proportionally. For ad slots, work with your ad provider to determine the most common creative sizes and reserve that space. However, if the ad is smaller than the slot, you'll have empty space; if it's larger, it may overflow or cause shifts. To handle this, set overflow: hidden on the container and consider using a placeholder that matches the expected size. For embeds like tweets or videos, use a container with a fixed aspect ratio based on the typical embed dimensions.

Deferring Non-Critical Widgets

Not all third-party content needs to load immediately. Defer widgets that are below the fold or not essential for the initial user experience. Use the 'loading=lazy' attribute for iframes, and consider using Intersection Observer to load widgets only when they are close to entering the viewport. This reduces the number of shifts during the critical rendering path. For example, a chatbot widget that appears in the bottom right can be loaded after the main content is stable. However, be careful: if the widget shifts after user interaction, that shift is excluded from CLS, but if it shifts before any interaction, it counts.

Using Sandboxing and Styling to Limit Impact

Some third-party scripts can modify the DOM outside their container, causing unexpected shifts. To prevent this, use the HTML 'sandbox' attribute on iframes to restrict their capabilities. For scripts that are injected directly into the page, consider using a shadow DOM to isolate their styles and layout. Additionally, set max-height and overflow: auto on the container to prevent the widget from expanding beyond a certain size. This won't eliminate shifts entirely, but it will cap the impact.

Monitoring and Auditing Third-Party Behavior

Since third-party scripts can change without notice, regular monitoring is essential. Use tools like the Chrome DevTools Performance panel to record page loads and check for shifts caused by third-party content. Set up synthetic monitoring that alerts you when CLS exceeds a threshold. Also, consider using a service like Request Metrics or SpeedCurve that provides real user monitoring (RUM) with CLS breakdowns by element. If a particular widget consistently causes high CLS, reach out to the provider or consider an alternative.

Third-party scripts are a major source of layout shifts, but they can be tamed with careful containment, deferral, and monitoring. By treating them as unpredictable guests and giving them a reserved, bounded space, you can maintain a stable layout for your users.

Responsive Images and Aspect Ratios: Avoiding the Most Common CLS Trap

Images are the single largest contributor to layout shifts on most pages. The classic advice—always set width and height—is correct, but it's often applied incorrectly, especially in responsive designs. The trap is that fixed dimensions don't translate well to flexible layouts, leading to shifts when the image loads and the browser recalculates the size. This section explains how to use the 'aspect-ratio' CSS property and the 'sizes' attribute to reserve space accurately across all viewports.

The Problem with Only Setting Width and Height

If you set width='800' and height='600' on an img tag, and then use CSS to set max-width: 100% and height: auto, the browser initially reserves 800x600 pixels. But when the viewport is smaller, the image scales down, and the height becomes auto-calculated based on the image's intrinsic aspect ratio. However, until the image loads, the browser doesn't know the intrinsic aspect ratio, so it may use the HTML attributes to compute an aspect ratio (800/600 = 1.33). This works in modern browsers, but if the actual image has a different aspect ratio (e.g., a 16:9 image), the reserved space will be wrong, causing a shift when the image loads. The fix is to ensure the HTML attributes always match the actual aspect ratio of the image file.

Using the aspect-ratio CSS Property

The most reliable method is to set aspect-ratio on the image or its container. For example, if all your images in a gallery are 4:3, you can set .gallery img { aspect-ratio: 4/3; width: 100%; height: auto; }. The browser reserves vertical space equal to width * (3/4) immediately, regardless of the image's intrinsic size. When the image loads, it fits inside that box, and no shift occurs. This works even if the image is not loaded yet, and it handles responsive scaling perfectly. You can also combine aspect-ratio with object-fit: cover to crop the image if needed.

Specifying sizes for Responsive Images

When using srcset, the browser needs to know which image to download based on the viewport and pixel density. The 'sizes' attribute tells the browser how wide the image will be displayed at different breakpoints. If you omit 'sizes', the browser assumes the image is 100vw wide, which may cause the downloaded image to be too large or too small, but it doesn't directly cause layout shifts. However, if the wrong image is downloaded, the intrinsic dimensions may differ from the reserved space, especially if you use aspect-ratio. Always specify 'sizes' to ensure the browser picks the correct source, which may have a different aspect ratio if you use different crops.

Handling Unknown Aspect Ratios with Containers

In some cases, you may not know the aspect ratio in advance, such as with user-uploaded images. In that case, you can use a container with a fixed height or use JavaScript to set the aspect ratio after the image loads, but that may cause a shift. A better approach is to use object-fit: contain within a container that has a fixed aspect ratio (e.g., 16:9) and let the image be centered with letterboxing. This ensures the container never changes size, and the image scales to fit without causing a shift. Alternatively, you can use the CSS property 'contain: layout style paint' on the container to isolate it.

By mastering aspect-ratio and responsive image techniques, you can eliminate image-induced shifts entirely. The key is to reserve space proportionally, not absolutely, and to ensure that the reserved aspect ratio matches the actual content.

Share this article:

Comments (0)

No comments yet. Be the first to comment!