Skip to content
intermediatePhase 38 · Web Performance

Cumulative Layout Shift

Prevent CLS with dimension attributes, font loading, and dynamic content.

30m
0 problems
Topic Progress0%

What is CLS

What is CLS

Cumulative Layout Shift measures the sum of all unexpected layout shifts during page lifecycle.

How CLS is Calculated

CLS = Impact Fraction × Distance Fraction
  • Impact Fraction: How much of the viewport is affected
  • Distance Fraction: How far the element shifts

Measuring CLS

import { onCLS } from 'web-vitals';

onCLS((metric) => {
  console.log('CLS:', metric.value);
  console.log('Rating:', metric.rating);
  
  // Get layout shift entries
  metric.entries.forEach((entry) => {
    console.log('Shift:', entry);
  });
});

Thresholds

Rating Threshold
Good ≤ 0.1
Needs Improvement ≤ 0.25
Poor > 0.25

Common Causes

  1. Images without dimensions
  2. Ads/embeds with dynamic size
  3. Dynamically injected content
  4. Web fonts causing FOIT/FOUT
  5. Late-loading JavaScript that changes layout

Debugging CLS

  1. Open Chrome DevTools → Performance panel
  2. Enable "Layout Shift Regions" in rendering tab
  3. Record page load
  4. Yellow highlighted areas show layout shifts
  5. Use Layout Shift debugger in DevTools

Preventing Layout Shift

Preventing Layout Shift

Set Image Dimensions

// Before: No dimensions
<img src="/photo.jpg" alt="Photo" />

// After: With dimensions
<img
  src="/photo.jpg"
  alt="Photo"
  width={800}
  height={600}
  style={{ aspectRatio: '800/600' }}
/>

// Responsive images
<img
  src="/photo.webp"
  srcset="/photo-400.webp 400w, /photo-800.webp 800w"
  sizes="(max-width: 768px) 100vw, 50vw"
  width={800}
  height={600}
  alt="Photo"
/>

Reserve Space for Ads

function AdBanner({ ad }) {
  return (
    <div className="ad-container" style={{ minHeight: 250 }}>
      {ad ? (
        <img
          src={ad.imageUrl}
          alt={ad.alt}
          width={ad.width}
          height={ad.height}
        />
      ) : (
        <div className="ad-placeholder">Advertisement</div>
      )}
    </div>
  );
}

CSS Aspect Ratio

/* Modern approach */
.video-container {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Fallback */
.video-container {
  width: 100%;
  padding-bottom: 56.25%; /* 9/16 * 100 */
  position: relative;
}

.video-container > * {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Skeleton Screens

function ContentCard({ isLoading, content }) {
  if (isLoading) {
    return (
      <div className="card skeleton" style={{ height: 300 }}>
        <div className="skeleton-image" style={{ height: 200 }} />
        <div className="skeleton-text" style={{ height: 24, marginTop: 16 }} />
        <div className="skeleton-text" style={{ height: 24, width: '60%' }} />
      </div>
    );
  }

  return (
    <div className="card" style={{ height: 300 }}>
      <img
        src={content.image}
        alt={content.title}
        width={400}
        height={200}
      />
      <h3>{content.title}</h3>
      <p>{content.excerpt}</p>
    </div>
  );
}

content-visibility

/* Skip rendering of off-screen content */
.below-fold {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}

Font Loading

Font Loading

FOIT vs FOUT

FOIT (Flash of Invisible Text):

  • Text is invisible until font loads
  • Bad for CLS if layout shifts when font appears

**FOUT (Flash of Unstyled Text):

  • Fallback font shown immediately
  • Text may shift when custom font loads

font-display

/* Optional: Show fallback, swap when ready */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
}

/* Other values */
font-display: auto; /* Browser default */
font-display: block; /* Hide text for 3s */
font-display: swap; /* Show fallback immediately */
font-display: fallback; /* Show fallback, swap if ready quickly */
font-display: never; /* Use fallback only */

Font Preloading

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

Size-Adjust

/* Match fallback font metrics */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%;
  ascent-override: 90%;
  descent-override: 20%;
  line-gap-override: 0%;
}

Font Loading Strategy

// Preload critical fonts
document.fonts.preload('/fonts/regular.woff2');

// Wait for fonts before measuring
await document.fonts.ready;

// Check if font is loaded
const isLoaded = document.fonts.check('16px CustomFont');

CLS Prevention Checklist

  • Set width/height on all images
  • Use aspect-ratio for containers
  • Reserve space for ads/embeds
  • Use font-display: swap
  • Preload critical fonts
  • Use skeleton screens
  • Avoid dynamically injecting content above the fold

Practice Problems

0/3solved
Build Cumulative Layout Shift Component

Create a reusable React component implementing Cumulative Layout Shift. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Cumulative Layout Shift Testing

Write unit and integration tests for Cumulative Layout Shift using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Cumulative Layout Shift Performance

Optimize Cumulative Layout Shift for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What is a good CLS score?

Question 1 options

2. What does CLS measure?

Question 2 options

3. What is FOIT?

Question 3 options

4. How does font-display: swap help CLS?

Question 4 options

5. Why should images always have width and height?

Question 5 options

Flashcards

Question

What is CLS?

Answer

Cumulative Layout Shift - measures the sum of unexpected layout shifts during page lifecycle.

Question

What is FOIT?

Answer

Flash of Invisible Text - text hidden until custom font loads.

Question

What is font-display: swap?

Answer

A CSS property that shows fallback text immediately and swaps to custom font when ready.

Question

Why do images need width and height?

Answer

To allow the browser to reserve space and prevent layout shifts when images load.

Question

What is Cumulative Layout Shift?

Answer

Cumulative Layout Shift is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.CLS measures visual stability with a good threshold of ≤ 0.1
  • 2.Always set dimensions on images to reserve space
  • 3.Use font-display: swap to prevent FOIT
  • 4.Reserve space for ads and dynamically loaded content
  • 5.Skeleton screens help maintain layout stability

Interview Tips

  • Explain what CLS measures and why it matters
  • Discuss techniques for preventing layout shift
  • Know the difference between FOIT and FOUT

Cheat Sheet

CLS Cheat Sheet

Threshold

  • Good: ≤ 0.1
  • Needs Improvement: ≤ 0.25
  • Poor: > 0.25

Prevention

  1. Set width/height on images
  2. Use aspect-ratio for containers
  3. Reserve space for ads/embeds
  4. Use font-display: swap
  5. Preload critical fonts
  6. Use skeleton screens

Common Causes

  • Images without dimensions
  • Ads with dynamic size
  • Late-loading fonts
  • Dynamic content injection