Book me

To reduce CLS in WordPress, reserve space for everything before it loads (images, embeds, ad slots, banners) and stop web fonts and late CSS from reflowing the page. CLS (Cumulative Layout Shift) scores unexpected movement of visible content and reports the largest burst of shifts during the visit. Google rates 0.1 or less at the 75th percentile as good and more than 0.25 as poor. On WordPress the usual sources are images without width and height in theme templates, font swaps, cookie and promo bars inserted above the header, ads and embeds, and optimization plugins that delay or strip CSS.

CLS is the Core Web Vital that annoys people most. You go to tap a link and the page moves, and you tap an ad instead. It is also the one with the most mechanical fixes. Once you know which element moves, the fix is usually a few lines of HTML or CSS. For the other two metrics and the overall fix order, see Core Web Vitals for WordPress: the complete guide.

How is CLS calculated?

Each layout shift gets a score from two numbers: how much of the viewport the moving elements affect (impact fraction) and how far they move relative to the viewport (distance fraction). Shifts are grouped into session windows. A window ends after one second without shifts, or after five seconds in total. CLS is the score of the worst window, not the sum of the whole visit.

Two details explain most confusion about CLS:

  • Shifts within 500 milliseconds of a user input (a click, tap or key press) are excluded. Opening an accordion when the user taps it is fine. Scrolling is not an input for this rule.
  • CLS is measured during the whole visit in the field, but Lighthouse only watches the load. Shifts caused by lazy content, ads that load while scrolling or sticky headers that change height show up in CrUX and not in your lab test.

That second point is why I never close a CLS issue based on Lighthouse alone. More on the difference in Core Web Vitals field data vs lab data.

What causes layout shifts in WordPress?

CauseWhere it comes fromFix
Images without dimensionsTheme templates, builder widgets, custom loopsOutput width and height, or set aspect-ratio in CSS
Web font swapFonts with different metrics from the fallback fontMetric overrides on the fallback, preload the main font, consider font-display: optional
Banners inserted at the topCookie consent, promo bars, notification pluginsOverlay them with fixed positioning, or render the space in the server HTML
Ads without reserved spaceAd scripts injecting variable-size creativesFixed min-height per slot, sized to the most common creative
EmbedsYouTube, social posts, maps, iframesResponsive embed wrappers with a set aspect ratio
Late or stripped CSS“Remove unused CSS”, delayed CSS, async stylesheetsTest on each template; keep the styles for the first screen in the critical path
Sliders and carouselsHeight set only after the script runsFixed height or aspect ratio before initialization
Animations on layout propertiesAnimating top, left, height or marginAnimate transform and opacity instead

WordPress handles part of the image problem for you. Core adds width and height to images in post content, and wp_get_attachment_image() outputs them too. The shifts I find in audits usually come from code that bypasses both: theme templates that build img tags by hand, builder widgets, and plugins that inject images with JavaScript.

For embeds, block themes and classic themes that declare add_theme_support( 'responsive-embeds' ) get core styles that keep the aspect ratio of embedded videos. Without that support, an embed can load at one size and then resize.

How do I find the element that shifts?

  • In PageSpeed Insights, the Lighthouse audit for large layout shifts lists the elements that moved during the lab load.
  • In the Chrome DevTools Performance panel, recorded layout shifts appear in their own track, and selecting one highlights the element.
  • The attribution build of the web-vitals library reports the element behind the largest shift from real visits, including shifts that happen after load.

Field attribution matters most for CLS, because many shifts only happen after the user scrolls. If Lighthouse shows a CLS of 0 and Search Console says your mobile pages are poor, the shift happens later in the visit.

When a web font replaces the fallback font, text reflows if the two fonts have different widths or line heights. Every line break can move, and everything below it moves too.

  • Self-host the font instead of loading it from a third-party CSS file. Block themes can register fonts in theme.json under fontFace, including the fontDisplay value, and since WordPress 6.5 the Font Library installs fonts on your own server.
  • Preload the one or two font files used above the fold.
  • Adjust the fallback font with size-adjust, ascent-override and descent-override in an @font-face rule, so it takes up the same space as the web font.
  • Use font-display: optional when the brand can accept the fallback on a slow first visit. It avoids the late swap entirely.

Font loading also affects LCP when the LCP element is a heading, so this work often helps both metrics. See how to improve LCP in WordPress for the loading side.

Can caching and optimization plugins cause CLS?

Yes, and often. Features that remove unused CSS, delay CSS until interaction, or load stylesheets asynchronously can render the page before its styles arrive. The browser paints unstyled content, the styles land, and everything jumps. Generated critical CSS that misses a component does the same.

Test these features on every template type, on a real phone, with a cold cache. If one template breaks, exclude its styles from the optimization instead of turning the feature off everywhere. The same plugins can also hurt responsiveness when they delay scripts, which I cover in how to fix INP in WordPress.

Why do sticky headers and cart counters shift the page?

A header that switches from static to fixed when the visitor scrolls leaves the document flow, and the content below jumps up by the header’s height. Make the header sticky from the start with position: sticky, or keep a placeholder of the same height in its place. Small elements shift too. A header cart count or a greeting that JavaScript fills in after load can change the width of everything next to it. Give these elements a fixed width, or render their final content in the HTML when the page cache allows it.

Does the back/forward cache help CLS?

When a visitor goes back or forward, Chrome can restore the page from the back/forward cache (bfcache) instantly, with no new load and so no new load shifts. Pages that send Cache-Control: no-store or use unload event handlers can lose that eligibility. DevTools has a back/forward cache test in the Application panel that tells you whether a page qualifies and why not.

How do I verify a CLS fix?

Reproduce the shift in DevTools, apply the fix and confirm the shift is gone from the layout shift track. Then watch real user data for the template, including scroll behavior. CrUX needs 28 days to fully reflect the change in PageSpeed Insights and Search Console.

Still seeing layout shifts?

If your CLS fails in the field and you can’t reproduce it, the shift is probably happening after load, in a component nobody tests. I find it in field data and trace it back to the theme, plugin or script that causes it. Learn more about my Core Web Vitals audits for WordPress.

Work with Daniel More in Performance