WordPress CLS (Cumulative Layout Shift) Troubleshooting
Cumulative Layout Shift (CLS) measures visual stability. WordPress sites often suffer from CLS due to dynamic ad insertion, web fonts, theme layouts, and plugin-injected content. This guide provides WordPress-specific solutions.
What is CLS?
CLS is a Core Web Vital that measures unexpected layout shifts during page load.
Thresholds:
- Good: < 0.1
- Needs Improvement: 0.1 - 0.25
- Poor: > 0.25
Common CLS Causes on WordPress:
- Images without width/height attributes
- Web fonts loading (FOUT/FOIT)
- Dynamic ad insertion
- Cookie consent banners
- Lazy-loaded images
- Embeds (YouTube, Twitter, Instagram)
Measuring WordPress CLS
Google PageSpeed Insights
- Go to PageSpeed Insights
- Enter your WordPress URL
- View Cumulative Layout Shift metric
- Check Diagnostics → Avoid large layout shifts for specific elements
Chrome DevTools
Layout Shift Regions (Visual):
- Open DevTools → Performance tab
- Check Experience checkbox
- Click Record and reload page
- Look for red "Layout Shift" markers
- Click marker to see which element shifted
Console Logging:
// Log all layout shifts
let cls = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
console.log('Layout Shift:', entry.value, 'Total CLS:', cls);
console.log('Shifted elements:', entry.sources);
}
}
}).observe({type: 'layout-shift', buffered: true});
Web Vitals Chrome Extension
Install Web Vitals to see CLS on every WordPress page.
Common WordPress CLS Issues
1. Images Without Dimensions
Symptoms:
- Content jumps down as images load
- Blog post images cause shifts
- WooCommerce product images shift layout
Solution: Always Set Width & Height
WordPress 5.5+ automatically adds width/height to images, but check:
// Ensure WordPress adds width/height attributes
add_filter('wp_img_tag_add_width_and_height_attr', '__return_true');
For existing images without dimensions:
// Automatically add dimensions to images in content
add_filter('the_content', 'add_img_dimensions');
function add_img_dimensions($content) {
// Match img tags without width/height
preg_match_all('/<img[^>]+>/', $content, $images);
foreach ($images[0] as $image) {
// Skip if already has width/height
if (strpos($image, 'width=') !== false || strpos($image, 'height=') !== false) {
continue;
}
// Extract src
preg_match('/src="([^"]+)"/', $image, $src);
if (!isset($src[1])) {
continue;
}
// Get image dimensions
$attachment_id = attachment_url_to_postid($src[1]);
if ($attachment_id) {
$image_meta = wp_get_attachment_metadata($attachment_id);
if ($image_meta) {
$new_image = str_replace('<img', '<img width="' . $image_meta['width'] . '" height="' . $image_meta['height'] . '"', $image);
$content = str_replace($image, $new_image, $content);
}
}
}
return $content;
}
CSS aspect-ratio (modern solution):
/* In your theme CSS */
img {
max-width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height); /* Prevents shift even before load */
}
Or use explicit aspect-ratio:
.featured-image {
aspect-ratio: 16 / 9; /* For 16:9 images */
width: 100%;
height: auto;
}
2. Lazy Loading Images
Symptoms:
- Images above the fold cause layout shifts
- Lazy load placeholder → full image causes jump
Solutions:
Don't Lazy Load Above-the-Fold Images
// Disable lazy loading for featured images (often above fold)
add_filter('wp_lazy_loading_enabled', 'disable_lazy_load_featured', 10, 2);
function disable_lazy_load_featured($default, $tag_name) {
if ('img' === $tag_name && is_singular() && has_post_thumbnail()) {
return false;
}
return $default;
}
Or manually control:
<!-- Above the fold: eager loading -->
<img src="hero.jpg" width="1200" height="600" loading="eager" alt="Hero">
<!-- Below the fold: lazy loading -->
<img src="content.jpg" width="800" height="400" loading="lazy" alt="Content">
Use Placeholder with Same Dimensions
If using lazy load plugin (Lazy Load by WP Rocket, etc.):
// Ensure placeholder has same dimensions as final image
add_filter('lazyload_placeholder_image', 'custom_lazy_placeholder');
function custom_lazy_placeholder($placeholder) {
// Use a tiny transparent GIF with same aspect ratio
return 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"%3E%3C/svg%3E';
}
3. Web Fonts Loading (FOUT/FOIT)
Symptoms:
- Text appears, then changes font (Flash of Unstyled Text)
- Text invisible, then appears (Flash of Invisible Text)
- Headers/body text jumps when font loads
Solutions:
Use font-display: swap
Fastest solution - show fallback font immediately:
@font-face {
font-family: 'Your Custom Font';
src: url('your-font.woff2') format('woff2');
font-display: swap; /* Show fallback immediately, swap when loaded */
}
For Google Fonts:
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
Preload Critical Fonts
add_action('wp_head', 'preload_fonts', 1);
function preload_fonts() {
?>
<link rel="preload" href="<?php echo get_stylesheet_directory_uri(); ?>/fonts/primary-font.woff2" as="font" type="font/woff2" crossorigin>
<?php
}
Match Fallback Font Metrics
Reduce layout shift by matching fallback font size to web font:
body {
font-family: 'Your Web Font', Arial, sans-serif;
/* Adjust line-height and letter-spacing to match web font */
line-height: 1.5;
letter-spacing: 0.02em;
}
Or use Fallback Font Generator to create perfect fallback.
Use System Fonts (No CLS)
Eliminate web fonts entirely:
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
4. Dynamic Ad Insertion
Symptoms:
- Content jumps as ads load
- Sidebar ads push content down
- In-content ads shift paragraphs
Solutions:
Reserve Ad Space with Placeholder
/* Reserve exact space for ads */
.ad-container {
min-height: 250px; /* Match ad unit height */
min-width: 300px; /* Match ad unit width */
background: #f5f5f5; /* Placeholder color */
}
For responsive ads:
.ad-container {
aspect-ratio: 300 / 250; /* Common ad size */
width: 100%;
max-width: 300px;
}
Use AdSense Auto Ads Carefully
Google AdSense Auto Ads can cause CLS. Solutions:
- Disable Auto Ads, use manually placed units instead
- Reserve space for common Auto Ad placements
- Use AdSense Anchor Ads only (sticky ads, less CLS)
Plugin: Ad Inserter allows precise ad placement with reserved space.
5. Cookie Consent Banners
Symptoms:
- Banner pushes content down on first visit
- Banner appears after page loads
Solutions:
Position Banner as Overlay
Don't push content - overlay instead:
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
/* This doesn't push content, no CLS */
}
Load Banner Immediately in <head>
add_action('wp_head', 'inline_cookie_banner_css', 1);
function inline_cookie_banner_css() {
?>
<style>
.cookie-banner {
position: fixed;
bottom: 0;
width: 100%;
background: #333;
color: #fff;
padding: 15px;
text-align: center;
z-index: 9999;
transform: translateY(100%); /* Hidden initially */
}
.cookie-banner.show {
transform: translateY(0); /* Slide up without CLS */
}
</style>
<?php
}
Popular consent plugins that minimize CLS:
- CookieYes - Overlay-based
- Complianz - Pre-rendered banner
- Cookie Notice - Simple, overlay
6. WordPress Theme Layout Issues
Symptoms:
- Header height changes after load
- Sidebar jumps
- Navigation menu shifts
Solutions:
Set Explicit Header Height
.site-header {
height: 80px; /* Fixed height */
}
.site-logo img {
height: 60px; /* Fixed logo height */
width: auto;
}
Reserve Sidebar Space
.sidebar {
min-height: 500px; /* Prevent collapsing */
}
.widget {
margin-bottom: 30px;
}
Fix Navigation Height
.main-navigation {
height: 60px;
line-height: 60px;
}
.main-navigation ul {
margin: 0;
padding: 0;
}
7. Embeds (YouTube, Twitter, Instagram)
Symptoms:
- Video embeds cause layout jump
- Social media embeds shift content
Solutions:
Use Aspect Ratio Containers
For YouTube embeds:
<div style="position: relative; padding-bottom: 56.25%; height: 0;">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
frameborder="0"
allowfullscreen>
</iframe>
</div>
Or use CSS:
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
WordPress Block Editor (Gutenberg)
Gutenberg automatically wraps embeds, but verify:
<!-- WordPress should output this -->
<div class="wp-block-embed__wrapper">
<iframe...></iframe>
</div>
If not, add wrapper manually in theme:
add_filter('embed_oembed_html', 'wrap_embed_with_aspect_ratio', 10, 3);
function wrap_embed_with_aspect_ratio($html, $url, $attr) {
if (strpos($url, 'youtube.com') !== false || strpos($url, 'vimeo.com') !== false) {
return '<div class="video-container">' . $html . '</div>';
}
return $html;
}
8. Infinite Scroll / AJAX Loading
Symptoms:
- Content loads below, pushes existing content up
- Footer jumps as more posts load
Solutions:
Reserve Space for Loaded Content
// When loading more posts via AJAX
jQuery.ajax({
url: ajaxurl,
beforeSend: function() {
// Add placeholder with estimated height
jQuery('.posts-container').append('<div class="loading-placeholder" style="height: 500px; background: #f5f5f5;"></div>');
},
success: function(data) {
// Remove placeholder, add real content
jQuery('.loading-placeholder').remove();
jQuery('.posts-container').append(data);
}
});
Use Pagination Instead
Infinite scroll inherently causes CLS. Consider traditional pagination for better CLS.
9. WordPress Plugins Causing CLS
Common Culprits:
Sliders (Revolution Slider, LayerSlider, etc.)
- Load slowly, cause header shifts
- Solution: Use CSS-only sliders, or reserve exact slider height
.slider-container {
height: 500px; /* Fixed height */
overflow: hidden;
}
Related Posts Plugins
- Load after content, push footer
- Solution: Load related posts server-side (not AJAX), or reserve space
Social Sharing Buttons
- Load asynchronously, shift content
- Solution: Use static SVG icons instead of widget scripts
// Remove social widget scripts that cause CLS
add_action('wp_enqueue_scripts', 'remove_social_scripts', 100);
function remove_social_scripts() {
wp_dequeue_script('addthis-widget');
wp_dequeue_script('sharethis-widget');
}
10. WooCommerce CLS Issues
Symptoms:
- Product images shift on product pages
- Cart updates cause layout jumps
- Checkout page shifts as fields load
Solutions:
Set WooCommerce Image Dimensions
add_action('after_setup_theme', 'woocommerce_image_dimensions');
function woocommerce_image_dimensions() {
add_theme_support('woocommerce', array(
'thumbnail_image_width' => 300,
'single_image_width' => 600,
'product_grid' => array(
'default_rows' => 3,
'min_rows' => 1,
'max_rows' => 8,
'default_columns' => 4,
'min_columns' => 1,
'max_columns' => 6
)
));
}
Reserve Product Gallery Height
.woocommerce-product-gallery {
min-height: 500px; /* Prevents shift while gallery loads */
}
.woocommerce-product-gallery__image {
aspect-ratio: 1 / 1; /* Square product images */
}
Fix Cart Update Shifts
.woocommerce-cart-form {
min-height: 300px; /* Prevent collapse during AJAX update */
}
Advanced WordPress CLS Fixes
Eliminate Render-Blocking CSS
// Inline critical CSS, defer the rest
add_action('wp_head', 'inline_critical_css', 1);
function inline_critical_css() {
?>
<style>
/* Critical CSS for above-the-fold content */
body { margin: 0; font-family: sans-serif; }
.site-header { height: 80px; background: #fff; }
/* ... */
</style>
<?php
}
// Defer non-critical CSS
add_action('wp_enqueue_scripts', 'defer_non_critical_css');
function defer_non_critical_css() {
wp_dequeue_style('theme-style');
add_action('wp_footer', function() {
echo '<link rel="stylesheet" href="' . get_stylesheet_uri() . '" media="print" onload="this.media=\'all\'">';
});
}
Preload Key Resources
add_action('wp_head', 'preload_cls_critical_resources', 1);
function preload_cls_critical_resources() {
?>
<!-- Preload fonts -->
<link rel="preload" href="<?php echo get_stylesheet_directory_uri(); ?>/fonts/primary.woff2" as="font" type="font/woff2" crossorigin>
<!-- Preload hero image -->
<?php if (is_front_page() && has_post_thumbnail()) : ?>
<link rel="preload" as="image" href="<?php echo get_the_post_thumbnail_url(null, 'full'); ?>">
<?php endif; ?>
<?php
}
Testing CLS Improvements
Before/After Testing
-
- Run PageSpeed Insights
- Record CLS score
- Note which elements shift
Make Changes
Verify:
- Clear all caches
- Re-run PageSpeed Insights
- Use Chrome DevTools Performance to visualize shifts
Real User Monitoring
Install Google Analytics 4 Web Vitals tracking:
add_action('wp_footer', 'track_web_vitals');
function track_web_vitals() {
?>
<script>
// Track CLS with GA4
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
gtag('event', 'web_vitals', {
metric_name: 'CLS',
metric_value: entry.value,
metric_id: entry.id
});
}
}
}).observe({type: 'layout-shift', buffered: true});
</script>
<?php
}
WordPress CLS Checklist
- All images have width & height attributes
- Above-the-fold images not lazy loaded
- Web fonts use
font-display: swap - Critical fonts preloaded
- Ad containers have reserved space
- Cookie banner uses fixed/overlay positioning
- Header has explicit height
- Embeds wrapped in aspect-ratio containers
- WooCommerce images have fixed dimensions
- No render-blocking CSS above the fold
- Slider containers have fixed height
- Sidebar has min-height
- Social widgets use static icons (not async scripts)
- Test with Chrome DevTools Performance tab
WordPress Plugins That Help CLS
Performance Plugins:
- WP Rocket - Defers CSS/JS, lazy loading with CLS prevention
- Perfmatters - Disables unused features causing CLS
- Asset CleanUp - Controls script loading to prevent shifts
Image Optimization:
- ShortPixel - Automatic width/height, WebP
- Imagify - Dimension preservation, lazy load config
CLS-Specific:
- Flying Scripts - Delays scripts until interaction (reduces CLS from async scripts)
Next Steps
- Fix LCP Issues - Largest Contentful Paint
- Debug Tracking Issues
- Review Global CLS Guide - Universal CLS concepts
Related Resources
- WordPress Performance
- WooCommerce Performance
- Core Web Vitals - Universal performance metrics