{
    "slug": "cumulative_layout_shift",
    "term": "Cumulative Layout Shift (CLS)",
    "category": "performance",
    "difficulty": "intermediate",
    "short": "A Core Web Vital that measures unexpected visual movement of page elements during load — caused by images without dimensions, late-injected content, and font swaps. Target: under 0.1.",
    "long": "CLS quantifies how much page content unexpectedly moves during the loading phase, frustrating users who click on the wrong element as the page shifts. The score is calculated as the sum of impact fraction × distance fraction for each unexpected layout shift. Target: under 0.1 (Good), 0.1-0.25 (Needs Improvement), over 0.25 (Poor). Common causes: images and iframes without explicit width and height attributes (browser can't reserve space); ads, cookie banners, or embeds injected above existing content after load; web fonts that swap and cause text reflow; animations that use top/left instead of transform (which is GPU-composited and doesn't cause layout recalculation). CLS only counts unexpected shifts — layout shifts caused by user interaction (clicking a button that expands content) within 500ms of the interaction are excluded from the score.",
    "aliases": [
        "CLS",
        "layout shift",
        "visual stability",
        "content jump",
        "page jump",
        "layout instability"
    ],
    "tags": [
        "performance",
        "frontend",
        "web-vitals",
        "core-web-vitals"
    ],
    "misconception": "CLS only matters if images are loading slowly — layout shift happens even on fast connections when content is injected dynamically or images lack dimensions; a fast connection just means the shift happens faster, not that it doesn't happen.",
    "why_it_matters": "A user reading an article on a mobile phone clicks what they believe is a link — but an ad loads above it at that exact moment, shifting the content down, and they click the ad instead. This is the exact user experience CLS measures and penalises.",
    "common_mistakes": [
        "Images without explicit width and height attributes — browser cannot reserve space, images shift content when loaded.",
        "Ads, analytics, or cookie consent banners injected above existing content after load — always inject into reserved space.",
        "Web font swap causing text reflow — use font-display: optional to prevent FOUT that shifts layout.",
        "CSS animations using top/left/margin instead of transform — property changes that trigger layout recalculation cause CLS; transform does not.",
        "Dynamic content insertion above the fold without reserved height — reserve minimum height with CSS before content loads."
    ],
    "when_to_use": [
        "Measure CLS in field data (CrUX) not just Lighthouse — real user device and connection conditions reveal shifts that lab tests miss.",
        "Use Chrome DevTools Performance panel with Layout Shift Regions overlay to visually identify what is shifting."
    ],
    "avoid_when": [
        "Do not animate layout properties (margin, padding, top, left, width, height) — use transform and opacity instead, which are GPU-composited and do not trigger CLS."
    ],
    "related": [
        "core_web_vitals",
        "largest_contentful_paint",
        "lazy_loading_images",
        "image_optimization",
        "web_vitals"
    ],
    "prerequisites": [],
    "refs": [
        "https://web.dev/articles/cls",
        "https://web.dev/articles/optimize-cls",
        "https://developer.chrome.com/docs/lighthouse/performance/cls"
    ],
    "bad_code": "/* CLS from images without dimensions */\n<img src=\"banner.jpg\" alt=\"Banner\"><!-- no width/height -->\n\n/* CLS from late-injected banner */\n<script>\n  // Runs after page load — pushes content down\n  document.body.prepend(cookieBanner);\n</script>\n\n/* CLS from CSS animation */\n.slide-in { animation: slide 0.3s; }\n@keyframes slide { from { margin-top: -100px; } to { margin-top: 0; } }",
    "good_code": "<!-- Images with explicit dimensions — browser reserves space -->\n<img src=\"banner.jpg\" alt=\"Banner\" width=\"1200\" height=\"400\">\n\n<!-- Reserved space for cookie banner -->\n<div style=\"min-height: 60px\" id=\"cookie-banner-slot\">\n  <!-- Banner injects here without shifting content -->\n</div>\n\n/* Use transform instead of layout properties for animation */\n.slide-in { animation: slide 0.3s; }\n@keyframes slide { from { transform: translateY(-100px); } to { transform: translateY(0); } }",
    "quick_fix": "Add explicit width and height to every img and iframe; reserve space for ads and injected content; use transform for CSS animations instead of top/left/margin",
    "severity": "high",
    "effort": "low",
    "created": "2026-04-06",
    "updated": "2026-04-06",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/cumulative_layout_shift",
        "html_url": "https://codeclaritylab.com/glossary/cumulative_layout_shift",
        "json_url": "https://codeclaritylab.com/glossary/cumulative_layout_shift.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Cumulative Layout Shift (CLS)](https://codeclaritylab.com/glossary/cumulative_layout_shift) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/cumulative_layout_shift"
            }
        }
    }
}