Fix Shopify Cumulative Layout Shift (CLS)
Cumulative Layout Shift (CLS) measures visual stability. Layout shifts frustrate users and hurt conversion rates, especially on product and collection pages.
Target: CLS under 0.1 Good: Under 0.1 | Needs Improvement: 0.1-0.25 | Poor: Over 0.25
For general CLS concepts, see the global CLS guide.
Shopify-Specific CLS Issues
1. Product Images Without Dimensions
The most common CLS issue on Shopify is product images loading without reserved space.
Problem: Images load and push content down as they appear.
Diagnosis:
- Run PageSpeed Insights
- Look for "Image elements do not have explicit width and height"
- Watch product/collection pages load and observe shifts
Solutions:
A. Add Width and Height Attributes
Always specify image dimensions:
<!-- Before: No dimensions -->
<img src="{{ product.featured_image | image_url: width: 800 }}" alt="{{ product.title }}">
<!-- After: With dimensions -->
<img
src="{{ product.featured_image | image_url: width: 800 }}"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
alt="{{ product.title }}"
>
For responsive images, use aspect ratio:
<img
src="{{ product.featured_image | image_url: width: 800 }}"
width="800"
height="{{ 800 | divided_by: product.featured_image.aspect_ratio | round }}"
alt="{{ product.title }}"
loading="lazy"
>
B. Use Aspect Ratio CSS (Modern Approach)
<div class="product-image" style="aspect-ratio: {{ product.featured_image.aspect_ratio }};">
<img
src="{{ product.featured_image | image_url: width: 800 }}"
alt="{{ product.title }}"
loading="lazy"
>
</div>
.product-image {
width: 100%;
overflow: hidden;
}
.product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
C. Dawn Theme Product Images
In Dawn theme, edit sections/main-product.liquid:
{%- for media in product.media -%}
<img
srcset="{{ media | image_url: width: 375 }} 375w,
{{ media | image_url: width: 750 }} 750w,
{{ media | image_url: width: 1100 }} 1100w"
src="{{ media | image_url: width: 1100 }}"
alt="{{ media.alt | escape }}"
width="{{ media.width }}"
height="{{ media.height }}"
loading="lazy"
>
{%- endfor -%}
2. Collection Grid Images
Collection pages often have multiple images loading simultaneously.
Solution: Reserve Space for Grid Items
<!-- Collection product grid -->
<div class="product-grid">
{% for product in collection.products %}
<div class="product-card">
<div class="product-image-wrapper" style="aspect-ratio: 1;">
<img
src="{{ product.featured_image | image_url: width: 400 }}"
width="400"
height="400"
alt="{{ product.title }}"
loading="lazy"
>
</div>
<h3 class="product-title">{{ product.title }}</h3>
<p class="product-price">{{ product.price | money }}</p>
</div>
{% endfor %}
</div>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.product-image-wrapper {
width: 100%;
position: relative;
overflow: hidden;
}
.product-image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
}
3. Font Loading Layout Shifts
Custom fonts can cause layout shifts as they load.
Problem: Text reflows when custom font replaces fallback font.
Solutions:
A. Use font-display: swap
@font-face {
font-family: 'CustomFont';
src: url('{{ 'custom-font.woff2' | asset_url }}') format('woff2');
font-display: swap; /* Prevents invisible text, may cause shift */
font-weight: 400;
font-style: normal;
}
B. Use font-display: optional (Better for CLS)
@font-face {
font-family: 'CustomFont';
src: url('{{ 'custom-font.woff2' | asset_url }}') format('woff2');
font-display: optional; /* Prevents layout shift, may use fallback */
font-weight: 400;
}
C. Match Fallback Font Metrics
Use a fallback font that closely matches your custom font's metrics:
body {
font-family: 'CustomFont', Arial, sans-serif;
/* Adjust fallback to match custom font */
font-size-adjust: 0.5;
}
D. Preload Fonts
In theme.liquid <head>:
<link
rel="preload"
href="{{ 'custom-font.woff2' | asset_url }}"
as="font"
type="font/woff2"
crossorigin
>
E. Use Shopify Font Picker
Shopify's native font picker automatically optimizes font loading:
- Theme Editor → Theme Settings → Typography
- Fonts are automatically served from Shopify CDN
- Better optimized than custom fonts
4. Shopify App-Induced Layout Shifts
Apps are a major cause of CLS on Shopify stores.
Common Culprits:
A. Trust Badges / Social Proof Apps
- "X people viewing this product"
- "Recently purchased" notifications
- Review stars appearing late
B. Popup / Email Capture Apps
- Popups pushing content down
- Slide-in banners
- Announcement bars appearing late
C. Wishlist / Compare Apps
- Buttons added to product cards after load
- Icons appearing above product images
D. Live Chat Widgets
- Chat bubble shifting footer
- Widget expanding unexpectedly
Solutions:
A. Reserve Space for App Elements
If you must use apps that inject content:
/* Reserve space for trust badge */
.product-trust-badge {
min-height: 40px; /* Reserve space */
}
/* Reserve space for review stars */
.product-reviews {
min-height: 24px;
}
B. Load Apps After Page Load
Delay app scripts to prevent shifts:
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
// Load app script here
const script = document.createElement('script');
script.src = 'https://app-domain.com/widget.js';
document.body.appendChild(script);
}, 1000); // Delay 1 second
});
C. Replace Apps with Native Features
Instead of apps:
- Use Shopify's native product reviews (Settings → Apps)
- Use static trust badges (add to theme, not via app)
- Use GTM for analytics (not individual app pixels)
D. Audit and Remove Problematic Apps
Test CLS with apps disabled:
- Temporarily disable apps one by one
- Test CLS after each
- Identify biggest offenders
Find alternatives:
- Look for lighter apps
- Use native Shopify features
- Implement features in theme code
5. Dynamic Content and Injected Elements
Content that appears after page load causes shifts.
Common Issues:
A. Cart Drawer Opening
- Cart sliding in from side
- Pushes content or overlays unexpectedly
Solution: Use CSS transforms (don't shift layout)
.cart-drawer {
position: fixed;
top: 0;
right: 0;
transform: translateX(100%); /* Off-screen */
transition: transform 0.3s ease;
z-index: 1000;
}
.cart-drawer.open {
transform: translateX(0); /* Slide in without shifting layout */
}
B. Announcement Bars
- Appearing after page load
- Pushing header down
Solution: Reserve space or use fixed positioning
.announcement-bar {
position: sticky;
top: 0;
min-height: 40px; /* Reserve space even when empty */
z-index: 100;
}
C. Dynamic Pricing / Inventory
- "Only X left" messages
- Price changes (sales, discounts)
Solution: Reserve space with min-height
<div class="product-price" style="min-height: 28px;">
{{ product.price | money }}
</div>
<div class="inventory-message" style="min-height: 20px;">
{% if product.inventory_quantity < 10 %}
Only {{ product.inventory_quantity }} left!
{% endif %}
</div>
6. Shopify Section Rendering
Shopify sections can cause CLS if not structured properly.
Issue: Sections Loading Sequentially
Sections appear one by one, each pushing content down.
Solution: Optimize Section Rendering
A. Use Placeholder Heights
<!-- In section schema settings -->
{
"name": "Featured Collection",
"settings": [
{
"type": "range",
"id": "section_height",
"min": 300,
"max": 800,
"step": 50,
"default": 500,
"label": "Section height"
}
]
}
<!-- In section liquid -->
<div class="featured-collection" style="min-height: {{ section.settings.section_height }}px;">
<!-- Content -->
</div>
B. Critical CSS for Above-Fold Sections
Inline CSS for first sections in <head>:
<style>
.hero-section {
min-height: 600px;
width: 100%;
}
</style>
7. Product Variant Changes
Switching variants can cause shifts if not handled properly.
Issue: Product Image Changes Size
Different variants have different image sizes.
Solution: Fixed Aspect Ratio Container
<div class="product-images">
{% for image in product.images %}
<div class="product-image-container" style="aspect-ratio: 1;">
<img
src="{{ image | image_url: width: 800 }}"
alt="{{ image.alt }}"
data-variant-image="{{ image.variant_ids | join: ',' }}"
loading="lazy"
>
</div>
{% endfor %}
</div>
.product-image-container {
width: 100%;
position: relative;
overflow: hidden;
}
.product-image-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
8. Mobile-Specific CLS Issues
Mobile devices often experience worse CLS due to slower connections and smaller viewports.
Common Mobile Issues:
A. Mobile Navigation Menu
- Menu appearing late
- Hamburger icon shifting
Solution: Reserve space for mobile header
@media (max-width: 768px) {
.header {
min-height: 60px; /* Reserve mobile header space */
}
.mobile-menu-toggle {
width: 44px; /* Explicit size for tap target */
height: 44px;
}
}
B. Mobile Product Images
- Larger shifts on small screens
- Zoom features causing shifts
Solution: Fixed aspect ratio especially important on mobile
<div class="product-image-mobile" style="aspect-ratio: {{ product.featured_image.aspect_ratio }};">
<img
src="{{ product.featured_image | image_url: width: 750 }}"
alt="{{ product.title }}"
>
</div>
C. Mobile Sticky Elements
- Sticky "Add to Cart" bar
- Sticky header shrinking/expanding
Solution: Use fixed positioning, not sticky (for critical elements)
.mobile-add-to-cart {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px; /* Fixed height */
z-index: 100;
}
Testing CLS
Tools
1. PageSpeed Insights
- Lab data (simulated)
- Field data (real users)
- Identifies specific shifting elements
2. Chrome DevTools
- More Tools → Rendering → Layout Shift Regions
- Highlights shifting elements in real-time
- Visual debugging
3. Web Vitals Extension
- Chrome Extension
- Shows CLS score in real-time
- Helps identify when shifts occur
4. WebPageTest
- Filmstrip view shows shifts frame-by-frame
- Compare before/after optimizations
Manual Testing Process
- Open Chrome DevTools (F12)
- Enable "Layout Shift Regions":
- More Tools → Rendering → Check "Layout Shift Regions"
- Reload page and watch for blue highlights
- Test interactions:
- Scroll down page
- Switch product variants
- Add to cart
- Open cart drawer
- Note which elements shift
- Fix highest-impact shifts first
Test Multiple Scenarios
- Fresh page load (clear cache)
- Slow 3G connection (throttle in DevTools)
- Mobile devices (real device testing)
- Different product types (with/without multiple images)
- Empty vs full cart
Quick Wins Checklist
- Add width and height to all product images
- Add width and height to collection grid images
- Use
aspect-ratioCSS for image containers - Set
font-display: optionalfor custom fonts - Reserve space for app-injected elements
- Fix announcement bar positioning
- Optimize cart drawer to not shift layout
- Use fixed heights for dynamic content areas
- Test with Layout Shift Regions enabled
- Uninstall/replace problematic apps
Shopify Theme-Specific Issues
Dawn Theme
- Generally good CLS
- May need image dimension fixes in custom sections
- Check third-party app integrations
Legacy Themes (Debut, Brooklyn, etc.)
- Often missing image dimensions
- Older CSS may not use aspect-ratio
- Recommendation: Update to Dawn or OS 2.0 theme
Custom Themes
- Audit thoroughly
- Ensure all images have dimensions
- Test all interactive elements (cart, navigation, modals)
Common CLS Patterns in Shopify
| Page Type | Common CLS Source | Fix Priority |
|---|---|---|
| Homepage | Hero image, announcement bar | Highest |
| Collection | Product grid images | High |
| Product | Product images, reviews app | Highest |
| Cart | Cart drawer, dynamic totals | Medium |
| Checkout | Shopify-controlled (limited fixes) | Low |
When to Hire a Developer
Consider hiring a Shopify Expert if:
- CLS consistently over 0.25 after basic optimizations
- Custom theme has complex layout issues
- Apps deeply integrated into theme
- Need to rebuild sections without shifts
- Require advanced CSS/JavaScript optimization
Find Shopify Experts: shopify.com/partners
Monitoring CLS Over Time
Chrome User Experience Report (CrUX):
- Real user CLS data
- Available in PageSpeed Insights
- 28-day rolling average
- Core Web Vitals report
- Shows pages with poor CLS
- SEO impact warnings
Continuous Monitoring:
- SpeedCurve
- Calibre
- DebugBear
- Set up alerts for CLS regressions
Next Steps
For general CLS optimization strategies, see CLS Optimization Guide.