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
- Distance fraction measures how far the unstable element moved relative to the viewport
- 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 score for a shift is:
- CLS is cumulative, meaning every unexpected shift during the session window adds up
- Good CLS: below
0.1 - Poor CLS: above
0.25
- Good CLS: below
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
widthandheight - Or use modern CSS:
aspect-ratio: 16 / 9;
- Always specify
- This allows the browser to allocate space before the asset loads
- The browser needs to know how much space to reserve
- 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
- In this strategy:
- 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
- The core problem is font loading time, so the best strategy is to preload fonts and and use
- 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
- When loading data asynchronously: