Cumulative Layout Shift (CLS)

Technical implementation guide for Core Web Vitals optimization. Learn to prevent layout shifts through proper image sizing, container reservation, and web font strategies for improved user experience.

Understanding Cumulative Layout Shift

Cumulative Layout Shift (CLS) is one of Google's Core Web Vitals metrics, measuring the visual stability of a webpage by quantifying how much visible content shifts unexpectedly during page loading. Unlike Largest Contentful Paint and INP which measure timing, CLS is a unitless score calculated from two factors: the distance elements move (impact fraction) and the distance they move (distance fraction) according to Google's official documentation.

The metric matters because unpredictable layout shifts create poor user experiences. Users attempting to click buttons or read content encounter frustration when elements move beneath their cursor or scroll away. This directly impacts engagement metrics and can affect search rankings since Google uses Core Web Vitals as ranking signals for search results.

CLS Scoring Thresholds

Google defines three CLS performance categories:

  • Good: 0.1 or less - pages should strive for this threshold
  • Needs Improvement: Between 0.1 and 0.25 - room for optimization exists
  • Poor: Above 0.25 - significant user experience issues present

The calculation multiplies impact fraction (percentage of viewport affected) by distance fraction (percentage of element movement), producing a score that can theoretically reach 1.0 for severe layout instability. For most sites, achieving and maintaining CLS below 0.1 requires systematic attention to how page elements reserve space during loading.

CLS Performance Thresholds

0.1or less

Good Score Threshold

0.25+

Poor Score Threshold

1.0

Maximum Possible Score

Lab Data vs Field Data: Understanding the Discrepancy

A critical distinction in CLS measurement exists between lab tools and real-user data. Lab tools like Lighthouse simulate page loads under controlled conditions, measuring only shifts occurring during initial load. Field data from Chrome User Experience Report (CrUX) captures real-user experiences across varied network conditions, device capabilities, and user interactions.

This discrepancy often confuses developers who see excellent Lighthouse scores but poor CrUX performance. The explanation typically involves post-load shifts: lazy-loaded content expanding, infinite scroll triggering new items, or dynamic content injections after user interactions. Field data captures these real-world scenarios while lab tests do not.

The Page Lifecycle and CLS Measurement

CLS measures layout shifts throughout the entire page lifecycle, not just initial load. This comprehensive approach means shifts occurring during user scrolling, lazy-loading, or dynamic content updates all contribute to the final score. Only shifts occurring within 500ms of user-initiated interactions are excluded, as these represent expected responses rather than unstable content according to Google's Core Web Vitals documentation.

The distinction matters for SPA architectures where content loads progressively after initial render. Any content that causes other elements to shift--even if loading occurs seconds after initial paint--adds to the cumulative score. Understanding this helps prioritize fixes: addressing initial load shifts provides immediate improvement, but post-load shifts may contribute more significantly to real-user CLS scores.

Technical Setup: Preventing Layout Shifts

Image Dimension Specification

The most common cause of CLS involves images loaded without width and height attributes. When browsers encounter images without explicit dimensions, they cannot reserve appropriate space, causing content below to shift once image dimensions are known according to Google's optimization guide.

HTML Implementation:

The fundamental fix requires specifying both width and height attributes on img elements:

<!-- Fixed: Explicit dimensions prevent layout shift -->
<img src="product.jpg" width="640" height="480" alt="Product image">

<!-- Responsive images with dimension preservation -->
<img src="hero-800.jpg"
 srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
 sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
 width="800" height="600"
 alt="Hero image">

The browser uses these attributes to calculate aspect ratio and reserve space before image download begins. For responsive images, maintaining consistent aspect ratio across source variants prevents shifts when different sizes load as recommended in modern implementation guides.

CSS Aspect-Ratio Property:

Modern CSS provides the aspect-ratio property for explicit space reservation:

/* Reserve space for images without HTML dimensions */
img[loading="lazy"] {
 aspect-ratio: 16 / 9;
 width: 100%;
 height: auto;
}

/* Generic placeholder for dynamically loaded images */
.dynamic-image {
 aspect-ratio: 4 / 3;
 width: 100%;
 background-color: #f0f0f0;
}

The aspect-ratio property works with or without HTML dimensions, providing fallback space reservation for content management systems and third-party embeds where HTML modification isn't feasible.

Advertisement and Embed Container Sizing

Dynamic content like advertisements causes significant CLS when containers don't reserve space. Advertisements load asynchronously and often arrive with different dimensions than expected, making pre-sizing essential per Google's performance guidelines.

Advertisement Container Implementation:

/* Fixed-height ad container prevents shift */
.ad-container {
 min-height: 250px;
 width: 100%;
 background-color: #f8f9fa;
}

/* Responsive ad with aspect ratio */
.ad-unit {
 aspect-ratio: 728 / 90;
 max-width: 100%;
 min-height: 90px;
}

Skeleton states provide visual feedback while content loads, reducing perceived shift impact even when actual space reservation prevents layout movement as recommended in implementation best practices.

Iframe Embeds:

/* YouTube embed with aspect ratio */
.youtube-container {
 aspect-ratio: 16 / 9;
 width: 100%;
}

/* Map embed container */
.map-container {
 aspect-ratio: 600 / 450;
 width: 100%;
 min-height: 450px;
}

Dynamic Content Prevention Strategies

Content injected via JavaScript causes layout shifts when not anticipated. Prompts, notifications, and live chat widgets all require pre-allocated space. Our web development services include performance optimization strategies that address these common issues.

/* Fixed-position notification container */
.notification-container {
 position: fixed;
 top: 0;
 left: 0;
 right: 0;
 min-height: 60px;
}

/* Push-down banner pattern */
.banner-push {
 position: relative;
 min-height: 80px;
}

Web Font Loading and Layout Stability

Web fonts can cause layout shifts when fallback fonts differ significantly in size from web fonts. The phenomenon, called Flash of Unstyled Text (FOUT) or Flash of Invisible Text (FOIT), affects layout when font metrics differ as documented by Google.

Font Loading Strategies:

/* Font display swap for immediate fallback */
@font-face {
 font-family: 'CustomFont';
 src: url('/fonts/custom-font.woff2') format('woff2');
 font-display: swap;
}

/* Size-adjust for consistent metrics */
@font-face {
 font-family: 'CustomFont';
 src: url('/fonts/custom-font.woff2') format('woff2');
 font-display: swap;
 size-adjust: 95%; /* Adjust to match fallback metrics */
}

The size-adjust descriptor allows matching web font metrics to system font metrics, reducing layout variation when fonts swap as recommended in modern font loading guides.

CLS Prevention Techniques

Key implementation strategies for preventing layout shifts

Image Dimensions

Always specify width and height attributes on img elements to reserve space before image load.

CSS Aspect Ratio

Use aspect-ratio property for dynamic content and where HTML modification isn't possible.

Container Sizing

Reserve minimum height for ads, embeds, and dynamic content containers.

Font Loading

Implement font-display: swap with size-adjust for consistent metrics.

Skeleton States

Provide visual feedback during loading to reduce perceived instability.

SPA Considerations

Reserve space before route content loads in single-page applications.

Validation and Testing

Chrome DevTools Performance Panel

Chrome DevTools provides detailed layout shift visualization through the Performance panel. Recording a page load reveals layout shifts as purple bars on the Timing track, with each bar representing a shift event as documented by Google.

Layout Shift Visualization:

The Performance panel shows layout shifts with visual indicators:

  • Purple bars indicate layout shift events
  • Bar height corresponds to shift severity
  • Red flags mark significant shifts contributing to CLS score
  • Expanding individual shifts reveals affected elements

The Layout Shift Regions feature highlights shifting areas in real-time during page interaction, making it easy to identify problematic components without manual inspection.

PageSpeed Insights Analysis

PageSpeed Insights provides both lab (Lighthouse) and field (CrUX) CLS data, enabling comparison between simulated and real-user experiences per Google's documentation. The tool displays:

  • Overall CLS score with pass/fail indicator
  • Element-level breakdown of shift contributions
  • Comparison to origin-level and category-level benchmarks
  • Specific recommendations for improvement

Field data shows real-user CLS percentiles (75th percentile is the reporting threshold), while lab data provides controlled environment measurements for debugging.

Lighthouse CLS Audits

Lighthouse includes dedicated CLS audits identifying specific causes:

  1. Image elements without explicit width and height - Flags images missing dimension attributes
  2. Elements with dynamic dimensions - Identifies content with sizing dependent on external factors
  3. CLS contribution breakdown - Shows each element's contribution to total score

The audit output includes specific selectors and line numbers, enabling direct code modifications as detailed by DebugBear.

JavaScript Performance Observer for CLS
1// Monitor layout shifts in production2const observer = new PerformanceObserver((list) => {3 for (const entry of list.getEntries()) {4 if (!entry.hadRecentInput) {5 console.log('Layout shift:', {6 value: entry.value,7 hadRecentInput: entry.hadRecentInput,8 sources: entry.sources,9 timestamp: entry.startTime10 });11 }12 }13});14 15observer.observe({ type: 'layout-shift', buffered: true });

Web Vitals Library Integration

The web-vitals library provides a simple interface for tracking Core Web Vitals including CLS:

import { getCLS } from 'web-vitals';

getCLS((metric) => {
 // Log to console
 console.log('CLS:', metric.value);

 // Send to analytics
 sendToAnalytics({
 name: 'CLS',
 value: metric.value,
 rating: metric.rating,
 delta: metric.delta
 });
});

The library handles attribution, rating thresholds, and delta calculations automatically as recommended by Google's web.dev documentation.

Monitoring and Continuous Improvement

RUM Implementation for CLS Tracking

Real User Monitoring (RUM) provides continuous visibility into CLS performance across real user sessions. Implementing RUM requires:

  1. Data Collection: Deploy PerformanceObserver or web-vitals library across production
  2. Sampling Strategy: Sample sufficient users for statistical significance
  3. Segmentation: Track CLS by page type, device, network condition
  4. Alerting: Configure thresholds for significant CLS increases

Key Metrics to Track:

  • 75th percentile CLS score (Google's reporting threshold)
  • Percentage of sessions with good CLS
  • Distribution across page types
  • Trend over time
  • Correlation with conversion metrics

SPA-Specific CLS Considerations

Single Page Applications face unique CLS challenges due to dynamic content updates. Route changes can trigger layout shifts as new content replaces existing content as documented in performance guidelines.

SPA CLS Best Practices:

// Reserve space before route content loads
let reservedHeight = 0;

function reserveRouteSpace(height) {
 const container = document.getElementById('main-content');
 container.style.minHeight = `${height}px`;
 reservedHeight = height;
}

// After content renders, adjust if needed
function adjustReservedSpace() {
 const actualHeight = document.getElementById('main-content').scrollHeight;
 if (actualHeight > reservedHeight) {
 document.getElementById('main-content').style.minHeight = `${actualHeight}px`;
 }
}

Component-based reservation ensures new routes don't cause shifts as content loads. For complex SPA implementations, consider partnering with our AI automation services to integrate advanced monitoring solutions.

Frequently Asked Questions

Optimize Your Site's Core Web Vitals

Ensure your website meets Google's Core Web Vitals standards for better user experience and search rankings.

Implementation Checklist

Pre-Launch CLS Validation

  • All images have explicit width and height attributes
  • CSS aspect-ratio applied where HTML modification isn't possible
  • Ad and embed containers have fixed minimum dimensions
  • Dynamic content reserves space before loading
  • Web fonts use font-display: swap with size-adjust if needed
  • Lighthouse CLS audit passes (below 0.1)
  • Chrome DevTools Performance panel shows no major shifts

Production Monitoring Setup

  • Web-vitals library integrated for CLS tracking
  • RUM data collection configured with adequate sampling
  • Dashboard created for CLS trend monitoring
  • Alerting configured for CLS degradation
  • CLS data correlated with business metrics

Ongoing Optimization Process

  • Weekly CLS score review against targets
  • Post-load shift analysis and remediation
  • New feature CLS impact assessment
  • Third-party script CLS audit
  • Mobile-specific CLS optimization