Performance Monitoring

CLS Monitoring: Eliminate Cumulative Layout Shift for Better UX

Monitor and fix Cumulative Layout Shift (CLS) to eliminate frustrating visual instability. Learn what causes CLS, how to measure it, and how to fix it.

AzMonitor TeamMarch 25, 20257 min read · 1,086 wordsUpdated January 20, 2026
CLSCumulative Layout ShiftCore Web Vitalsperformance monitoring

Cumulative Layout Shift (CLS) is the Core Web Vital that measures visual stability — how much the page's content unexpectedly moves during loading. High CLS means elements jump around, text moves after you've started reading it, and buttons shift right as you're about to click them. It's one of the most frustrating user experience issues, and it directly impacts Google search rankings.

Understanding the CLS Score

CLS is a unitless score calculated from the sum of layout shift scores throughout the page's entire lifetime:

Layout Shift Score = Impact Fraction × Distance Fraction

Impact Fraction = Fraction of viewport affected by the shift
Distance Fraction = Largest distance shifted as a fraction of viewport height

CLS = Sum of all layout shift scores (excluding shifts within 500ms of user input)

CLS thresholds: | Score | Rating | Impact | |-------|--------|--------| | ≤ 0.1 | Good | Minimal visual disruption | | 0.1 – 0.25 | Needs Improvement | Noticeable shifts | | > 0.25 | Poor | Severe visual instability |

Note: CLS excludes shifts that happen within 500ms of user input (taps, clicks). A layout shift caused by user interaction (clicking a button that expands content) doesn't count against your score — only unexpected shifts count.

Common Causes of Layout Shift

1. Images Without Dimensions

The most common CLS cause. When a browser loads an image without knowing its dimensions, it allocates zero space for it initially. When the image loads, everything below it shifts down.

<!-- BAD: No dimensions specified -->
<img src="product.jpg" alt="Product">

<!-- GOOD: Explicit dimensions prevent layout shift -->
<img src="product.jpg" alt="Product" width="800" height="600">

Or use CSS aspect-ratio for responsive images:

/* Reserve space before image loads */
.product-image {
  aspect-ratio: 4/3;
  width: 100%;
}

2. Ads, Embeds, and Iframes Without Reserved Space

Ad containers that expand when an ad loads push content down. This is extremely common and very impactful on CLS scores.

/* Reserve space for ad slot */
.ad-container {
  min-height: 250px;  /* Expected ad height */
  width: 100%;
}

3. Dynamically Injected Content Above Existing Content

Pop-ups, banners, cookie notices, and other dynamically injected content that appears above existing page content causes everything below it to shift.

Solutions:

  • Reserve space for these elements in the initial layout
  • Use transforms/overlay positioning that doesn't affect flow
  • Use fixed positioning for banners that would otherwise push content

4. Web Fonts Causing FOUT/FOIT

Flash of Unstyled Text (FOUT) or Flash of Invisible Text (FOIT) occurs when web fonts load after text has been displayed in a fallback font. The font swap changes text dimensions, causing layout shifts.

/* Reduce font swap impact */
@font-face {
  font-display: optional;  /* Don't show font until available, no swap */
  /* OR: */
  font-display: swap;  /* Show fallback immediately, swap when ready */
}

Better: Preload critical fonts to reduce the time until the correct font is available:

<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

5. Animations That Use Non-Transform Properties

Animating top, left, margin, padding, or width/height triggers layout recalculation and can cause layout shifts.

/* BAD: Causes layout shift */
.slide-in {
  animation: slide 0.3s;
}
@keyframes slide {
  from { margin-top: -100px; }
  to { margin-top: 0; }
}

/* GOOD: Transforms don't affect layout */
.slide-in {
  animation: slide 0.3s;
}
@keyframes slide {
  from { transform: translateY(-100px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

Diagnosing High CLS

Use Chrome DevTools to identify which elements are shifting:

  1. Open DevTools → More Tools → Rendering
  2. Enable "Layout Shift Regions" (shows red rectangles on shifted elements)
  3. Reload the page and observe which elements shift

Alternatively, use the web-vitals library with attribution data:

import { onCLS } from 'web-vitals/attribution';

onCLS(({ value, attribution }) => {
  console.log('CLS score:', value);
  attribution.largestShiftEntry?.sources?.forEach(source => {
    console.log('Shifted element:', source.node);
    console.log('Shift value:', source.currentRect, '→', source.previousRect);
  });
});

Continuous CLS Monitoring

Lab Monitoring

CLS is measured in lab tools by running the page through a simulated load. AzMonitor's performance monitoring captures CLS alongside other Core Web Vitals:

performance_monitor:
  url: https://yoursite.com
  capture_metrics: [cls, lcp, inp, fcp, ttfb]
  cls_threshold: 0.1
  alert_if_above_threshold: true
  frequency: daily  # Or after each deployment

Important caveat: Lab CLS measurements may miss issues that only occur with real user behavior (scrolling, dynamic content based on user data). Field data from RUM is essential for complete CLS monitoring.

Field Data Monitoring

Real CLS issues often appear in specific scenarios: slow network connections where ads load late, specific user journeys that trigger dynamic content, or device types where fonts load differently.

import { onCLS } from 'web-vitals';

onCLS(({ value, id }) => {
  // Track segmented by page and user context
  fetch('/analytics/vitals', {
    method: 'POST',
    body: JSON.stringify({
      metric: 'cls',
      value,
      id,
      url: window.location.pathname,
      deviceType: /Mobile|iPhone|Android/i.test(navigator.userAgent) ? 'mobile' : 'desktop',
    }),
    headers: { 'Content-Type': 'application/json' }
  });
}, { reportAllChanges: true });  // Report each individual shift, not just final

CLS in Single Page Applications

SPAs have unique CLS challenges. Page transitions, data loading, and component mounting can all cause layout shifts:

Route transitions: When navigating to a new "page" in a SPA, new content loads asynchronously. Without skeleton screens or proper loading states, content shifts as it loads.

Data fetching: Components that fetch data and then expand to show results cause layout shift if no placeholder space is reserved.

// React example: Skeleton loading to prevent CLS
function ProductCard({ productId }) {
  const { product, loading } = useProduct(productId);
  
  if (loading) {
    return (
      // Fixed-height skeleton that reserves space
      <div className="product-card skeleton" style={{ height: '320px' }} />
    );
  }
  
  return (
    <div className="product-card">
      {/* product content */}
    </div>
  );
}

Setting CLS Targets by Page Type

Different page types have different CLS risk profiles:

| Page Type | CLS Risk | Target | |-----------|---------|--------| | Static marketing pages | Low | < 0.05 | | Blog/content pages | Medium (ads) | < 0.1 | | E-commerce product pages | High (images, reviews) | < 0.1 | | SPA dashboards | High (data loading) | < 0.1 | | News pages | Very high (ads, embeds) | < 0.1 (hard to achieve) |

Start monitoring CLS with AzMonitor alongside your other Core Web Vitals. Catch CLS regressions from deployment changes before they affect your Google search rankings.

Related: Core Web Vitals monitoring guide for complete CWV coverage.

Tags:CLSCumulative Layout ShiftCore Web Vitalsperformance monitoring
Back to blog
A
AzMonitor Team
The AzMonitor team writes guides based on experience monitoring millions of endpoints daily across 10,000+ customer environments. Our expertise covers uptime monitoring, SRE practices, and reliability engineering.
Try AzMonitor free

3 monitors free forever · No credit card needed · Set up in 2 minutes

Start monitoring free →