Cumulative Layout Shift (CLS)

The metric that measures visual stability and unexpected layout shifts.

Check Your Site's Cumulative Layout Shift

Enter your website URL on the homepage to see your real CLS performance data.

What is CLS?

Cumulative Layout Shift (CLS) measures the sum of all unexpected layout shifts that occur during the entire lifespan of a page. A layout shift occurs when a visible element changes its position from one rendered frame to the next.

CLS Thresholds

≤ 0.1

Good

≤ 0.25

Needs Improvement

> 0.25

Poor

Quick Wins for CLS

  1. Add width and height to all images
  2. Use aspect-ratio CSS property for responsive media
  3. Set min-height on ad/embed containers
  4. Add font-display: optional to web fonts
  5. Never insert content above existing content

How CLS is Calculated

The layout shift score is calculated by multiplying two factors:

Layout Shift Score = Impact Fraction × Distance Fraction

Impact Fraction

The combined area of all unstable elements in the viewport, divided by the total viewport area.

Distance Fraction

The greatest distance any unstable element moved, divided by the viewport's largest dimension.

Note: Layout shifts that occur within 500ms of user input are excluded from CLS calculations, as users expect the page to respond to their actions.

Common Causes of Poor CLS

1. Images Without Dimensions

When images load without explicit width and height attributes, the browser doesn't know how much space to reserve, causing content to shift.

2. Ads, Embeds, and Iframes

Third-party content often loads asynchronously with unknown dimensions, pushing existing content around when it appears.

3. Dynamically Injected Content

Content added to the DOM above existing content (like banners, cookie notices, or notifications) shifts everything below it down.

4. Web Fonts Causing FOIT/FOUT

When custom fonts load and replace fallback fonts, differences in sizing can cause text to shift (Flash of Invisible/Unstyled Text).

5. Animations Triggering Layout

CSS animations that change properties like width,height, or toptrigger layout recalculations and shifts.

How to Improve CLS

1. Always Include Size Attributes

  • Set width and height on images and videos
  • Use CSS aspect-ratio for responsive sizing
  • Reserve space for ads with min-height containers

Example: Image with dimensions

<!-- HTML with explicit dimensions -->
<img src="hero.jpg" width="800" height="450" alt="Hero">

<!-- CSS aspect-ratio for responsive -->
<style>
  .responsive-image {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
  }
</style>

2. Reserve Space for Dynamic Content

  • Use placeholder containers with fixed dimensions
  • Add skeleton screens while loading
  • Position dynamic content at the bottom or in overlays

Example: Ad container with reserved space

.ad-container {
  min-height: 250px; /* Reserve space for standard ad */
  background: #f0f0f0;
}

.skeleton {
  background: linear-gradient(
    90deg,
    #e0e0e0 25%,
    #f0f0f0 50%,
    #e0e0e0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

3. Optimize Web Font Loading

  • Use font-display: optional or swap
  • Preload critical fonts
  • Use fallback fonts with similar metrics
  • Consider using system fonts for body text

Example: Font loading best practices

<!-- Preload critical font -->
<link rel="preload" href="/fonts/brand.woff2"
      as="font" type="font/woff2" crossorigin>

<!-- CSS with fallback -->
@font-face {
  font-family: 'Brand';
  src: url('/fonts/brand.woff2') format('woff2');
  font-display: optional; /* Prevents FOUT */
  size-adjust: 100.5%; /* Match fallback size */
}

4. Use Transform for Animations

  • Use transform and opacity for animations
  • Avoid animating width, height, top, left
  • Use CSS will-change for complex animations

Good vs Bad animations

/* Bad: Triggers layout */
.slide-in {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { left: -100px; }
  to { left: 0; }
}
/* Good: Uses transform (no layout) */
.slide-in {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { transform: translateX(-100px); }
  to { transform: translateX(0); }
}

Measuring CLS

Using the web-vitals library

import { onCLS } from 'web-vitals';

onCLS((metric) => {
  console.log('CLS:', metric.value);

  // Log the sources of layout shift
  metric.entries.forEach(entry => {
    entry.sources?.forEach(source => {
      console.log('Shifted element:', source.node);
    });
  });

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