In some websites when we’re about to click a button and suddenly the page jumps, causing you to click something you never intended to. That exact rage-inducing behavior is what CLS measures

CLS stands for Cumulative Layout Shift. It measures how visually stable a webpage is while it loads. If elements like text, images, buttons, or ads move around unexpectedly during load, the CLS score increases
A high CLS score means the page is literally shifting under the user’s finger, leading to accidental clicks and poor user experience

A common misconception is that animations increase CLS. That is not true. Only unexpected layout shifts count toward CLS. If a user triggers the change (for example, clicking a button that opens a menu), it does not affect CLS. Proper CSS or JS animations that do not cause unexpected layout changes are also excluded

Q How CLS Is Measured ?
A CLS is calculated using two components:

  • Impact Fraction
    • Impact fraction measures how much of the viewport area is affected by unstable elements between two frames
    • Example:
      • An element initially occupies 50% of the viewport
      • In the next frame, it shifts by 25%
      • The combined affected area becomes 75% of the viewport
    • So, the impact fraction = 0.75
  • Distance Fraction
    • Distance fraction measures how far the unstable element moved relative to the viewport
      • If the element moved 25% of the viewport height
      • Distance fraction = 0.25
  • CLS Calculation for a Single Shift
    • CLS score for a shift is: CLS = Impact Fraction × Distance Fraction
    • So in this example: 0.75 × 0.25 = 0.1875
  • CLS is cumulative, meaning every unexpected shift during the session window adds up
    • Good CLS: below 0.1
    • Poor CLS: above 0.25
Real-World Causes of Bad CLS
  • Images Without Width and Height
    • When the browser lays out a page, it must reserve space for every element. If an image does not specify width and height, the browser assumes its size is zero
    • Later, when the image finishes loading:
      • The browser discovers the real height (e.g., 400px)
      • All content below gets pushed down
      • Text, buttons, and sections jump
    • Solution:
      • The browser needs to know how much space to reserve
        • Always specify width and height
        • Or use modern CSS:
          aspect-ratio: 16 / 9;
      • This allows the browser to allocate space before the asset loads
  • Ads and Embedded Iframes
    • Ads often load asynchronously
    • Typical behavior:
      • Placeholder loads with 0 height
      • Real ad arrives and expands
      • Everything below shifts downward
    • Even if an iframe loads early, the content inside it can resize later, triggering layout shifts
    • Solution:
      • Always reserve space before content loads
      • Use fixed width and height placeholders
      • Replace placeholders with the real ad
      • Skeleton loaders work well here
      • The key idea is that the layout should not change when content arrives
  • Web Fonts and Layout Shifts
    • Fonts cause CLS in two different ways, depending on loading strategy
    • FOIT - Flash of Invisible Text:
      • Some font strategies hide text until the custom font loads
      • What happens:
        • Browser reserves minimal or zero space
        • Text is invisible
        • Custom font loads
        • Text suddenly appears with real dimensions
        • Layout snaps into place
      • This causes a noticeable layout shift because invisible text becomes visible
    • FOUT - Flash of Unstyled Text:
      • In this strategy:
        • Fallback font is shown first
        • Custom font replaces it when ready
      • Problem:
        • Fallback and custom fonts have different sizes
        • Text blocks resize
        • Containers shift slightly left, right, or downward
      • Here, CLS happens due to text size changes, not invisibility
    • Solution:
      • The core problem is font loading time, so the best strategy is to preload fonts and and use font-display: swap
      • Preloading forces the browser to download fonts early, before JavaScript and other CSS
      • This results in:
        • No invisible text (no FOIT)
        • Minimal fallback usage
        • Font swap happens very fast (often <50ms)
        • No noticeable layout shift
  • JavaScript Inserting DOM Content
    • Any content inserted above existing content forces the browser to push everything down
    • Common examples:
      • Cookie consent banners
      • GDPR popups
      • Notification bars
    • Since these change the layout after render, they significantly increase CLS
    • Solution:
      • When loading data asynchronously:
        • Use skeleton placeholders
        • Reserve exact space upfront
      • When real content replaces the skeleton:
        • Layout stays stable
        • CLS remains zero