[{"data":1,"prerenderedAt":1209},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners":1200},{"id":4,"title":5,"body":6,"description":1180,"extension":1181,"meta":1182,"navigation":1194,"path":1195,"seo":1196,"stem":1198,"__hash__":1199},"content\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners\u002Findex.md","Fixing CLS from Cookie Consent Banners",{"type":7,"value":8,"toc":1166},"minimark",[9,14,29,49,162,167,221,225,231,237,253,259,363,367,372,375,544,556,560,563,634,637,641,649,704,707,711,732,840,843,932,936,939,1053,1057,1060,1070,1085,1091,1095,1111,1124,1128,1151,1156,1159,1162],[10,11,13],"h1",{"id":12},"how-to-stop-cookie-consent-banners-from-causing-layout-shift","How to Stop Cookie Consent Banners from Causing Layout Shift",[15,16,17,18,23,24,28],"p",{},"This guide addresses one of the most frequent CLS sources on European and privacy-regulated sites, within ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Reducing Cumulative Layout Shift (CLS)"," and the wider ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," section.",[15,30,31,32,36,37,40,41,44,45,48],{},"A consent management platform (CMP) script loads asynchronously, decides the visitor has not yet consented, and inserts a banner. If that banner is inserted into the normal document flow — at the top of ",[33,34,35],"code",{},"\u003Cbody>",", or as a static block above the footer that becomes visible — every element below it moves. Because the insertion happens seconds after load with no user input, the whole movement counts toward CLS. A 120px banner pushing a full mobile viewport of content down can single-handedly produce a shift score of ",[33,38,39],{},"0.15","–",[33,42,43],{},"0.3",", well past the ",[33,46,47],{},"0.1"," threshold, on every first visit.",[15,50,51],{},[52,53,59,60,59,67,59,71,59,74,59,83,59,89,59,98,59,104,59,109,59,114,59,117,59,120,59,123,59,126,59,129,59,132,59,137,59,141,59,143,59,147,59,149,59,152,59,154,59,157,59,159,59],"svg",{"viewBox":54,"width":55,"role":56,"ariaLabel":57,"style":58},"0 0 760 228","100%","img","Comparison of a consent banner inserted into document flow, which shifts content, with one positioned as a fixed overlay, which does not.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[61,62],"rect",{"className":63,"x":65,"y":65,"width":55,"height":55,"fill":66},[64],"svg-canvas","0","#ffffff",[68,69,70],"title",{},"In-flow banner vs overlay banner",[72,73,57],"desc",{},[61,75],{"x":76,"y":76,"width":77,"height":78,"rx":79,"fill":80,"stroke":81,"style":82},"1","758","226","10","none","currentColor","stroke-opacity:0.18",[84,85,70],"text",{"x":86,"y":87,"fill":81,"style":88},"28.0","34.0","font-size:16px;font-weight:700",[61,90],{"x":86,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":96,"style":97},"56.0","340.0","150.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[84,99,103],{"x":100,"y":101,"fill":81,"style":102},"42.0","82.0","font-size:14px;font-weight:700","In-flow banner (shifts)",[84,105,108],{"x":100,"y":106,"fill":81,"style":107},"108.0","font-size:12.5px;font-weight:700","•",[84,110,113],{"x":91,"y":106,"fill":81,"style":111,"textAnchor":112},"font-size:12.5px","start","Inserted at top of body after CMP loads",[84,115,108],{"x":100,"y":116,"fill":81,"style":107},"132.0",[84,118,119],{"x":91,"y":116,"fill":81,"style":111,"textAnchor":112},"Header, hero and text all move down 120px",[84,121,108],{"x":100,"y":122,"fill":81,"style":107},"156.0",[84,124,125],{"x":91,"y":122,"fill":81,"style":111,"textAnchor":112},"Shift counted: no recent input",[84,127,108],{"x":100,"y":128,"fill":81,"style":107},"180.0",[84,130,131],{"x":91,"y":128,"fill":81,"style":111,"textAnchor":112},"CLS 0.18 on first visits",[61,133],{"x":134,"y":91,"width":92,"height":93,"rx":94,"fill":135,"stroke":135,"style":136},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[84,138,140],{"x":139,"y":101,"fill":81,"style":102},"406.0","Fixed overlay (no shift)",[84,142,108],{"x":139,"y":106,"fill":81,"style":107},[84,144,146],{"x":145,"y":106,"fill":81,"style":111,"textAnchor":112},"420.0","position: fixed at the bottom of the viewport",[84,148,108],{"x":139,"y":116,"fill":81,"style":107},[84,150,151],{"x":145,"y":116,"fill":81,"style":111,"textAnchor":112},"Covers content, moves nothing",[84,153,108],{"x":139,"y":122,"fill":81,"style":107},[84,155,156],{"x":145,"y":122,"fill":81,"style":111,"textAnchor":112},"No layout-shift entries generated",[84,158,108],{"x":139,"y":128,"fill":81,"style":107},[84,160,161],{"x":145,"y":128,"fill":81,"style":111,"textAnchor":112},"CLS unaffected by the banner",[163,164,166],"h2",{"id":165},"rapid-diagnosis","Rapid Diagnosis",[168,169,170,178,184,209,215],"ul",{},[171,172,173,177],"li",{},[174,175,176],"strong",{},"Reproduce as a first-time visitor."," Open an incognito window (or clear the CMP's consent cookie) and record a Performance trace. Repeat visits hide the problem because consent is stored.",[171,179,180,183],{},[174,181,182],{},"Find the shift in the Layout Shifts track."," Click the shift cluster near the banner's appearance; the summary lists moved nodes. If the header, hero and main content all appear, an in-flow insertion is the cause.",[171,185,186,189,190,193,194,197,198,201,202,197,205,208],{},[174,187,188],{},"Inspect the banner container."," Check its computed ",[33,191,192],{},"position",". ",[33,195,196],{},"static"," or ",[33,199,200],{},"relative"," means it participates in layout; ",[33,203,204],{},"fixed",[33,206,207],{},"sticky"," at the viewport edge should not shift other content.",[171,210,211,214],{},[174,212,213],{},"Check for a second shift on dismissal."," Clicking \"Accept\" removes the banner. If it was in flow, content moves back up — but that shift follows input, so it is excluded. The first shift is the one that counts.",[171,216,217,220],{},[174,218,219],{},"Look at mobile."," Banners are proportionally much taller on phones; the same banner can be negligible on desktop and fatal on mobile.",[163,222,224],{"id":223},"root-cause-analysis","Root Cause Analysis",[15,226,227,230],{},[174,228,229],{},"1. In-flow insertion."," The CMP's default template, or a custom theme, renders the banner as a block element at the start of the body. Everything after it in the flow is displaced.",[15,232,233,236],{},[174,234,235],{},"2. A placeholder that collapses or expands."," Some sites reserve space for the banner in server HTML but size it differently from the real banner, or remove the reservation when consent is already stored and the CMP loads late. A mismatch in either direction produces a shift.",[15,238,239,242,243,197,246,249,250,252],{},[174,240,241],{},"3. Body padding adjustments."," Some CMPs add ",[33,244,245],{},"padding-bottom",[33,247,248],{},"padding-top"," to ",[33,251,35],{}," so the banner does not cover content. Changing body padding after render moves the entire page.",[15,254,255,258],{},[174,256,257],{},"4. Late font or style loading inside the banner."," If the banner is reserved correctly but its own stylesheet or font loads late, the banner can change height after insertion, shifting anything positioned relative to it.",[15,260,261],{},[52,262,59,265,59,268,59,271,59,273,59,276,59,278,59,282,59,288,59,295,59,299,59,305,59,310,59,314,59,321,59,327,59,331,59,335,59,339,59,343,59,347,59,351,59,355,59,359,59],{"viewBox":263,"width":55,"role":56,"ariaLabel":264,"style":58},"0 0 760 204","Timeline of a first visit in which the consent script loads after LCP and inserts an in-flow banner at 2.7 seconds, generating the page's largest layout shift.",[61,266],{"className":267,"x":65,"y":65,"width":55,"height":55,"fill":66},[64],[68,269,270],{},"When the banner shift happens",[72,272,264],{},[61,274],{"x":76,"y":76,"width":77,"height":275,"rx":79,"fill":80,"stroke":81,"style":82},"202",[84,277,270],{"x":86,"y":87,"fill":81,"style":88},[84,279,281],{"x":86,"y":280,"fill":81,"style":107},"74.0","Page",[61,283],{"x":284,"y":91,"width":285,"height":286,"rx":287,"fill":135,"stroke":135,"style":136},"73.9","308.6","26.0","3",[84,289,294],{"x":290,"y":291,"fill":81,"style":292,"textAnchor":293},"228.2","73.5","font-size:11.5px","middle","HTML, CSS, hero (LCP 1.9s)",[84,296,298],{"x":86,"y":297,"fill":81,"style":107},"112.0","CMP",[61,300],{"x":301,"y":302,"width":303,"height":286,"rx":287,"fill":81,"stroke":81,"style":304},"269.7","94.0","235.2","fill-opacity:0.06;stroke-opacity:0.4",[84,306,309],{"x":307,"y":308,"fill":81,"style":292,"textAnchor":293},"387.3","111.5","consent script",[61,311],{"x":312,"y":302,"width":313,"height":286,"rx":287,"fill":95,"stroke":96,"style":97},"506.4","23.0",[315,316],"line",{"x1":317,"y1":318,"x2":319,"y2":318,"stroke":81,"style":320},"73.1","134.0","726.0","stroke-opacity:0.4",[84,322,326],{"x":323,"y":324,"fill":325,"style":292,"textAnchor":293},"83.8","151.0","#51617a","0ms",[84,328,330],{"x":329,"y":324,"fill":325,"style":292,"textAnchor":293},"154.7","500ms",[84,332,334],{"x":333,"y":324,"fill":325,"style":292,"textAnchor":293},"236.4","1000ms",[84,336,338],{"x":337,"y":324,"fill":325,"style":292,"textAnchor":293},"318.0","1500ms",[84,340,342],{"x":341,"y":324,"fill":325,"style":292,"textAnchor":293},"399.6","2000ms",[84,344,346],{"x":345,"y":324,"fill":325,"style":292,"textAnchor":293},"481.2","2500ms",[84,348,350],{"x":349,"y":324,"fill":325,"style":292,"textAnchor":293},"562.8","3000ms",[84,352,354],{"x":353,"y":324,"fill":325,"style":292,"textAnchor":293},"644.4","3500ms",[84,356,358],{"x":357,"y":324,"fill":325,"style":292,"textAnchor":293},"712.3","4000ms",[84,360,362],{"x":86,"y":361,"fill":325,"style":111},"182.0","The insertion lands well after the 500ms input exclusion of any interaction, so the full movement counts toward CLS.",[163,364,366],{"id":365},"step-by-step-resolution","Step-by-Step Resolution",[368,369,371],"h3",{"id":370},"_1-render-the-banner-as-a-fixed-overlay","1. Render the banner as a fixed overlay",[15,373,374],{},"The most reliable fix is to take the banner out of document flow entirely. A fixed-position banner at the bottom of the viewport covers content temporarily but never moves it.",[376,377,382],"pre",{"className":378,"code":379,"language":380,"meta":381,"style":381},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast",".consent-banner {\n  position: fixed;\n  inset: auto 0 0 0;               \u002F* bottom of the viewport, full width *\u002F\n  z-index: 1000;\n  max-height: 40vh;\n  overflow: auto;\n  transform: translateY(0);\n}\n.consent-banner[hidden] { display: none; }\n\u002F* trade-off: an overlay covers part of the page until dismissed. Keep it compact\n   (max-height) so it does not obscure the LCP element on small screens, and make\n   sure it never covers focusable controls the user needs before consenting. *\u002F\n","css","",[33,383,384,396,410,435,448,465,477,496,502,526,532,538],{"__ignoreMap":381},[385,386,388,392],"span",{"class":315,"line":387},1,[385,389,391],{"class":390},"sPXB4",".consent-banner",[385,393,395],{"class":394},"saISM"," {\n",[385,397,399,402,405,407],{"class":315,"line":398},2,[385,400,401],{"class":390},"  position",[385,403,404],{"class":394},": ",[385,406,204],{"class":390},[385,408,409],{"class":394},";\n",[385,411,413,416,418,421,424,426,428,431],{"class":315,"line":412},3,[385,414,415],{"class":390},"  inset",[385,417,404],{"class":394},[385,419,420],{"class":390},"auto",[385,422,423],{"class":390}," 0",[385,425,423],{"class":390},[385,427,423],{"class":390},[385,429,430],{"class":394},";               ",[385,432,434],{"class":433},"sjfSM","\u002F* bottom of the viewport, full width *\u002F\n",[385,436,438,441,443,446],{"class":315,"line":437},4,[385,439,440],{"class":390},"  z-index",[385,442,404],{"class":394},[385,444,445],{"class":390},"1000",[385,447,409],{"class":394},[385,449,451,454,456,459,463],{"class":315,"line":450},5,[385,452,453],{"class":390},"  max-height",[385,455,404],{"class":394},[385,457,458],{"class":390},"40",[385,460,462],{"class":461},"sPARh","vh",[385,464,409],{"class":394},[385,466,468,471,473,475],{"class":315,"line":467},6,[385,469,470],{"class":390},"  overflow",[385,472,404],{"class":394},[385,474,420],{"class":390},[385,476,409],{"class":394},[385,478,480,483,485,488,491,493],{"class":315,"line":479},7,[385,481,482],{"class":390},"  transform",[385,484,404],{"class":394},[385,486,487],{"class":390},"translateY",[385,489,490],{"class":394},"(",[385,492,65],{"class":390},[385,494,495],{"class":394},");\n",[385,497,499],{"class":315,"line":498},8,[385,500,501],{"class":394},"}\n",[385,503,505,507,510,513,516,519,521,523],{"class":315,"line":504},9,[385,506,391],{"class":390},[385,508,509],{"class":394},"[",[385,511,512],{"class":390},"hidden",[385,514,515],{"class":394},"] { ",[385,517,518],{"class":390},"display",[385,520,404],{"class":394},[385,522,80],{"class":390},[385,524,525],{"class":394},"; }\n",[385,527,529],{"class":315,"line":528},10,[385,530,531],{"class":433},"\u002F* trade-off: an overlay covers part of the page until dismissed. Keep it compact\n",[385,533,535],{"class":315,"line":534},11,[385,536,537],{"class":433},"   (max-height) so it does not obscure the LCP element on small screens, and make\n",[385,539,541],{"class":315,"line":540},12,[385,542,543],{"class":433},"   sure it never covers focusable controls the user needs before consenting. *\u002F\n",[15,545,546,547,550,551,40,553,555],{},"Expected outcome: the banner produces no ",[33,548,549],{},"layout-shift"," entries; first-visit CLS falls by the full banner contribution, typically ",[33,552,47],{},[33,554,43],{}," on mobile.",[368,557,559],{"id":558},"_2-if-it-must-be-in-flow-reserve-exact-space-server-side","2. If it must be in flow, reserve exact space server-side",[15,561,562],{},"When regulation or design requires an in-flow banner (for example, a top notice that should not overlay the header), decide on the server whether consent is needed — the consent cookie is sent with the request — and render a correctly sized slot.",[376,564,568],{"className":565,"code":566,"language":567,"meta":381,"style":381},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003C!-- Server-side: render only when the consent cookie is absent. -->\n\u003Cdiv class=\"consent-slot\" style=\"min-height: 7.5rem\">\n  \u003C!-- CMP mounts into this element; the height is reserved from the first paint -->\n\u003C\u002Fdiv>\n\u003C!-- trade-off: the reserved height must match the real banner at every\n     breakpoint. Measure the banner at 360, 390 and 768px wide and set\n     min-height per breakpoint; one value for all widths will shift somewhere. -->\n","html",[33,569,570,575,605,610,619,624,629],{"__ignoreMap":381},[385,571,572],{"class":315,"line":387},[385,573,574],{"class":433},"\u003C!-- Server-side: render only when the consent cookie is absent. -->\n",[385,576,577,580,584,587,590,594,597,599,602],{"class":315,"line":398},[385,578,579],{"class":394},"\u003C",[385,581,583],{"class":582},"sZBmE","div",[385,585,586],{"class":390}," class",[385,588,589],{"class":394},"=",[385,591,593],{"class":592},"sZ8jY","\"consent-slot\"",[385,595,596],{"class":390}," style",[385,598,589],{"class":394},[385,600,601],{"class":592},"\"min-height: 7.5rem\"",[385,603,604],{"class":394},">\n",[385,606,607],{"class":315,"line":412},[385,608,609],{"class":433},"  \u003C!-- CMP mounts into this element; the height is reserved from the first paint -->\n",[385,611,612,615,617],{"class":315,"line":437},[385,613,614],{"class":394},"\u003C\u002F",[385,616,583],{"class":582},[385,618,604],{"class":394},[385,620,621],{"class":315,"line":450},[385,622,623],{"class":433},"\u003C!-- trade-off: the reserved height must match the real banner at every\n",[385,625,626],{"class":315,"line":467},[385,627,628],{"class":433},"     breakpoint. Measure the banner at 360, 390 and 768px wide and set\n",[385,630,631],{"class":315,"line":479},[385,632,633],{"class":433},"     min-height per breakpoint; one value for all widths will shift somewhere. -->\n",[15,635,636],{},"Expected outcome: the banner fills an existing space, so nothing moves when it appears. When consent is already given, no slot is rendered and nothing collapses.",[368,638,640],{"id":639},"_3-stop-the-cmp-from-changing-body-padding","3. Stop the CMP from changing body padding",[15,642,643,644,648],{},"Disable any CMP option that adjusts body padding or margins, and handle the overlay's footprint yourself — for example, by adding bottom padding to the page ",[645,646,647],"em",{},"from the first paint"," only when the banner will be shown.",[376,650,654],{"className":651,"code":652,"language":653,"meta":381,"style":381},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F Configure the CMP before its script loads (option names vary by vendor).\nwindow.__cmpConfig = { layout: 'overlay-bottom', adjustBodyPadding: false, animation: 'slide' };\n\u002F\u002F trade-off: vendor configuration names differ and change between versions.\n\u002F\u002F Pin the CMP version and re-test CLS on every upgrade — CMP updates are a\n\u002F\u002F frequent source of silent CLS regressions.\n","javascript",[33,655,656,661,689,694,699],{"__ignoreMap":381},[385,657,658],{"class":315,"line":387},[385,659,660],{"class":433},"\u002F\u002F Configure the CMP before its script loads (option names vary by vendor).\n",[385,662,663,666,668,671,674,677,680,683,686],{"class":315,"line":398},[385,664,665],{"class":394},"window.__cmpConfig ",[385,667,589],{"class":461},[385,669,670],{"class":394}," { layout: ",[385,672,673],{"class":592},"'overlay-bottom'",[385,675,676],{"class":394},", adjustBodyPadding: ",[385,678,679],{"class":390},"false",[385,681,682],{"class":394},", animation: ",[385,684,685],{"class":592},"'slide'",[385,687,688],{"class":394}," };\n",[385,690,691],{"class":315,"line":412},[385,692,693],{"class":433},"\u002F\u002F trade-off: vendor configuration names differ and change between versions.\n",[385,695,696],{"class":315,"line":437},[385,697,698],{"class":433},"\u002F\u002F Pin the CMP version and re-test CLS on every upgrade — CMP updates are a\n",[385,700,701],{"class":315,"line":450},[385,702,703],{"class":433},"\u002F\u002F frequent source of silent CLS regressions.\n",[15,705,706],{},"Expected outcome: no whole-page shift from padding changes.",[368,708,710],{"id":709},"_4-animate-in-with-transform-not-height","4. Animate in with transform, not height",[15,712,713,714,717,718,720,721,724,725,197,728,731],{},"If the banner animates into view, use ",[33,715,716],{},"transform: translateY(100%)"," → ",[33,719,65],{},". Animating ",[33,722,723],{},"height",", ",[33,726,727],{},"max-height",[33,729,730],{},"margin"," changes layout every frame and generates a shift per frame.",[376,733,735],{"className":378,"code":734,"language":380,"meta":381,"style":381},".consent-banner { transform: translateY(100%); transition: transform 200ms ease-out; }\n.consent-banner.is-visible { transform: translateY(0); }\n@media (prefers-reduced-motion: reduce) { .consent-banner { transition: none; } }\n\u002F* trade-off: transform animations are composited and shift-free, but the\n   element's layout box is in its final position from the start — so clicks\n   on the area it will cover are intercepted early. Use pointer-events: none\n   until the animation completes if that matters. *\u002F\n",[33,736,737,779,799,820,825,830,835],{"__ignoreMap":381},[385,738,739,741,744,747,749,751,753,756,759,762,765,768,771,774,777],{"class":315,"line":387},[385,740,391],{"class":390},[385,742,743],{"class":394}," { ",[385,745,746],{"class":390},"transform",[385,748,404],{"class":394},[385,750,487],{"class":390},[385,752,490],{"class":394},[385,754,755],{"class":390},"100",[385,757,758],{"class":461},"%",[385,760,761],{"class":394},"); ",[385,763,764],{"class":390},"transition",[385,766,767],{"class":394},": transform ",[385,769,770],{"class":390},"200",[385,772,773],{"class":461},"ms",[385,775,776],{"class":390}," ease-out",[385,778,525],{"class":394},[385,780,781,784,786,788,790,792,794,796],{"class":315,"line":398},[385,782,783],{"class":390},".consent-banner.is-visible",[385,785,743],{"class":394},[385,787,746],{"class":390},[385,789,404],{"class":394},[385,791,487],{"class":390},[385,793,490],{"class":394},[385,795,65],{"class":390},[385,797,798],{"class":394},"); }\n",[385,800,801,804,807,809,811,813,815,817],{"class":315,"line":412},[385,802,803],{"class":461},"@media",[385,805,806],{"class":394}," (prefers-reduced-motion: reduce) { ",[385,808,391],{"class":390},[385,810,743],{"class":394},[385,812,764],{"class":390},[385,814,404],{"class":394},[385,816,80],{"class":390},[385,818,819],{"class":394},"; } }\n",[385,821,822],{"class":315,"line":437},[385,823,824],{"class":433},"\u002F* trade-off: transform animations are composited and shift-free, but the\n",[385,826,827],{"class":315,"line":450},[385,828,829],{"class":433},"   element's layout box is in its final position from the start — so clicks\n",[385,831,832],{"class":315,"line":467},[385,833,834],{"class":433},"   on the area it will cover are intercepted early. Use pointer-events: none\n",[385,836,837],{"class":315,"line":479},[385,838,839],{"class":433},"   until the animation completes if that matters. *\u002F\n",[15,841,842],{},"Expected outcome: zero shift contribution from the entrance animation.",[15,844,845],{},[52,846,59,849,59,852,59,855,59,857,59,860,59,862,59,871,59,878,59,885,59,890,59,894,59,899,59,903,59,907,59,911,59,915,59,919,59,923,59,926,59],{"viewBox":847,"width":55,"role":56,"ariaLabel":848,"style":58},"0 0 760 216","Bar chart of first-visit CLS for four consent banner implementations, with only the in-flow version exceeding the 0.1 threshold.",[61,850],{"className":851,"x":65,"y":65,"width":55,"height":55,"fill":66},[64],[68,853,854],{},"First-visit CLS by banner implementation (mobile p75)",[72,856,848],{},[61,858],{"x":76,"y":76,"width":77,"height":859,"rx":79,"fill":80,"stroke":81,"style":82},"214",[84,861,854],{"x":86,"y":87,"fill":81,"style":88},[315,863],{"x1":864,"y1":865,"x2":864,"y2":866,"stroke":96,"strokeWidth":867,"strokeDashArray":868,"style":870},"430.1","72.0","194.0","1.5",[869,869],"4","stroke-opacity:0.9",[84,872,877],{"x":873,"y":874,"fill":81,"style":875,"textAnchor":876},"201.9","92.0","font-size:13px","end","In-flow, inserted by CMP",[61,879],{"x":880,"y":881,"width":882,"height":883,"rx":287,"fill":95,"stroke":96,"style":884},"213.9","78.0","454.1","19","fill-opacity:0.7;stroke-opacity:0.9",[84,886,889],{"x":887,"y":874,"fill":81,"style":888},"674.0","font-size:12px;font-weight:600","0.21",[84,891,893],{"x":873,"y":892,"fill":81,"style":875,"textAnchor":876},"123.0","In-flow, wrong reserved height",[61,895],{"x":880,"y":896,"width":897,"height":883,"rx":287,"fill":135,"stroke":135,"style":898},"109.0","151.4","fill-opacity:0.55;stroke-opacity:0.9",[84,900,902],{"x":901,"y":892,"fill":81,"style":888},"371.3","0.07",[84,904,906],{"x":873,"y":905,"fill":81,"style":875,"textAnchor":876},"154.0","In-flow, exact server slot",[61,908],{"x":880,"y":909,"width":910,"height":883,"rx":287,"fill":135,"stroke":135,"style":898},"140.0","21.6",[84,912,914],{"x":913,"y":905,"fill":81,"style":888},"241.5","0.01",[84,916,918],{"x":873,"y":917,"fill":81,"style":875,"textAnchor":876},"185.0","Fixed overlay",[61,920],{"x":880,"y":921,"width":922,"height":883,"rx":287,"fill":135,"stroke":135,"style":898},"171.0","2.0",[84,924,65],{"x":925,"y":917,"fill":81,"style":888},"221.9",[84,927,931],{"x":864,"y":928,"fill":929,"style":930,"textAnchor":293},"68.0","#8a6608","font-size:12px;font-weight:700","CLS 0.1",[163,933,935],{"id":934},"verification","Verification",[15,937,938],{},"Record a trace as a first-time visitor on a mobile viewport with CPU throttling, and confirm no layout shift entries coincide with the banner's appearance. Check at three widths (360, 390, 768px). In RUM, segment CLS by whether the consent cookie was present at page load — a field you can add to the beacon cheaply — and compare first-visit and returning-visit CLS. After the fix they should be close; before it, first-visit CLS is typically several times higher.",[376,940,942],{"className":651,"code":941,"language":653,"meta":381,"style":381},"import { onCLS } from 'web-vitals\u002Fattribution';\nconst firstVisit = !document.cookie.includes('consent=');\nonCLS(({ value, attribution }) => report({\n  cls: +value.toFixed(3), firstVisit, largestShiftTarget: attribution.largestShiftTarget,\n}));\n\u002F\u002F trade-off: reading document.cookie for a consent flag is fine for RUM tagging,\n\u002F\u002F but do not send the cookie value itself — only the boolean.\n",[33,943,944,960,988,1017,1038,1043,1048],{"__ignoreMap":381},[385,945,946,949,952,955,958],{"class":315,"line":387},[385,947,948],{"class":461},"import",[385,950,951],{"class":394}," { onCLS } ",[385,953,954],{"class":461},"from",[385,956,957],{"class":592}," 'web-vitals\u002Fattribution'",[385,959,409],{"class":394},[385,961,962,965,968,971,974,977,981,983,986],{"class":315,"line":398},[385,963,964],{"class":461},"const",[385,966,967],{"class":390}," firstVisit",[385,969,970],{"class":461}," =",[385,972,973],{"class":461}," !",[385,975,976],{"class":394},"document.cookie.",[385,978,980],{"class":979},"smZ65","includes",[385,982,490],{"class":394},[385,984,985],{"class":592},"'consent='",[385,987,495],{"class":394},[385,989,990,993,996,1000,1002,1005,1008,1011,1014],{"class":315,"line":412},[385,991,992],{"class":979},"onCLS",[385,994,995],{"class":394},"(({ ",[385,997,999],{"class":998},"sQw3B","value",[385,1001,724],{"class":394},[385,1003,1004],{"class":998},"attribution",[385,1006,1007],{"class":394}," }) ",[385,1009,1010],{"class":461},"=>",[385,1012,1013],{"class":979}," report",[385,1015,1016],{"class":394},"({\n",[385,1018,1019,1022,1025,1028,1031,1033,1035],{"class":315,"line":437},[385,1020,1021],{"class":394},"  cls: ",[385,1023,1024],{"class":461},"+",[385,1026,1027],{"class":394},"value.",[385,1029,1030],{"class":979},"toFixed",[385,1032,490],{"class":394},[385,1034,287],{"class":390},[385,1036,1037],{"class":394},"), firstVisit, largestShiftTarget: attribution.largestShiftTarget,\n",[385,1039,1040],{"class":315,"line":450},[385,1041,1042],{"class":394},"}));\n",[385,1044,1045],{"class":315,"line":467},[385,1046,1047],{"class":433},"\u002F\u002F trade-off: reading document.cookie for a consent flag is fine for RUM tagging,\n",[385,1049,1050],{"class":315,"line":479},[385,1051,1052],{"class":433},"\u002F\u002F but do not send the cookie value itself — only the boolean.\n",[163,1054,1056],{"id":1055},"the-consent-banner-and-your-other-metrics","The Consent Banner and Your Other Metrics",[15,1058,1059],{},"Fixing CLS is the most visible part of the problem, but consent banners affect the other Core Web Vitals too, and an overlay fix should not trade one metric for another.",[15,1061,1062,1065,1066,1069],{},[174,1063,1064],{},"LCP."," On small screens, the banner's text block can become the largest contentful element if it paints after the hero and covers a large area. An overlay that is too tall can therefore ",[645,1067,1068],{},"become"," your LCP element, with its timing dictated by the CMP script. Keep the banner compact and, ideally, render its markup server-side so its paint is early and predictable.",[15,1071,1072,1075,1076,1079,1080,1084],{},[174,1073,1074],{},"INP."," CMP scripts often run substantial work when the user clicks \"Accept\": writing cookies, firing queued tags, initialising analytics. That work runs in the click's processing phase and can push the interaction past ",[33,1077,1078],{},"200ms",". Defer tag firing after the banner closes — see ",[19,1081,1083],{"href":1082},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freducing-consent-management-platform-cost\u002F","reducing consent management platform cost",".",[15,1086,1087,1090],{},[174,1088,1089],{},"Load cost."," The CMP is typically one of the earliest third-party scripts and is render-critical for compliance in some configurations. Self-hosting it or loading it from a preconnected origin reduces its delay without changing its behaviour.",[163,1092,1094],{"id":1093},"faq","FAQ",[1096,1097,1100,1104],"details",{"className":1098},[1099],"faq-item",[1101,1102,1103],"summary",{},"Is a full-screen consent wall better for CLS?",[15,1105,1106,1107,1110],{},"A modal that overlays the page with ",[33,1108,1109],{},"position: fixed"," does not shift content, so CLS is fine. But consent walls block the page until dismissed and can make the wall itself the LCP element. They are a legal and UX decision first; from a performance standpoint, a compact bottom overlay is the safest default.",[1096,1112,1114,1117],{"className":1113},[1099],[1101,1115,1116],{},"Does the shift when the banner is dismissed count?",[15,1118,1119,1120,1123],{},"No, provided it happens within 500ms of the click — shifts with ",[33,1121,1122],{},"hadRecentInput"," are excluded. That is why the insertion shift is the one to fix. Be careful with dismissal animations that last longer than 500ms; frames after that window can count.",[163,1125,1127],{"id":1126},"related","Related",[168,1129,1130,1137,1144],{},[171,1131,1132,1136],{},[19,1133,1135],{"href":1134},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Fdebugging-cls-caused-by-dynamic-ad-injection\u002F","Debugging CLS caused by dynamic ad injection"," — the same late-insertion pattern from ad slots.",[171,1138,1139,1143],{},[19,1140,1142],{"href":1141},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffinding-layout-shift-sources-in-devtools\u002F","Finding layout shift sources in DevTools"," — confirming which node moved and why.",[171,1145,1146,1150],{},[19,1147,1149],{"href":1148},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-animating-layout-properties\u002F","Fixing CLS from animating layout properties"," — why transform beats height for entrance animations.",[1152,1153,1155],"script",{"type":1154},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Stop Cookie Consent Banners from Causing Layout Shift\",\n  \"description\": \"Why consent management banners cause layout shift, and the overlay, reservation and server-rendering patterns that eliminate it.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Render the banner as a fixed overlay\",\n      \"text\": \"The most reliable fix is to take the banner out of document flow entirely.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"If it must be in flow, reserve exact space server-side\",\n      \"text\": \"When regulation or design requires an in-flow banner (for example, a top notice that should not overlay the header), decide on the server whether consent is needed — the consent cookie is sent with the request — and render a correctly sized slot.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Stop the CMP from changing body padding\",\n      \"text\": \"Disable any CMP option that adjusts body padding or margins, and handle the overlay's footprint yourself — for example, by adding bottom padding to the page from the first paint only when the banner will be shown.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Animate in with transform, not height\",\n      \"text\": \"If the banner animates into view, use transform: translateY(100%) → 0.\"\n    }\n  ]\n}\n",[1152,1157,1158],{"type":1154},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Stop Cookie Consent Banners from Causing Layout Shift\",\n  \"description\": \"Why consent management banners cause layout shift, and the overlay, reservation and server-rendering patterns that eliminate it.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners\u002F\"\n  }\n}\n",[1152,1160,1161],{"type":1154},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Reducing Cumulative Layout Shift (CLS)\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Fixing CLS from Cookie Consent Banners\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners\u002F\"\n    }\n  ]\n}\n",[1163,1164,1165],"style",{},"html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":381,"searchDepth":398,"depth":398,"links":1167},[1168,1169,1170,1176,1177,1178,1179],{"id":165,"depth":398,"text":166},{"id":223,"depth":398,"text":224},{"id":365,"depth":398,"text":366,"children":1171},[1172,1173,1174,1175],{"id":370,"depth":412,"text":371},{"id":558,"depth":412,"text":559},{"id":639,"depth":412,"text":640},{"id":709,"depth":412,"text":710},{"id":934,"depth":398,"text":935},{"id":1055,"depth":398,"text":1056},{"id":1093,"depth":398,"text":1094},{"id":1126,"depth":398,"text":1127},"Why consent management banners cause layout shift, and the overlay, reservation and server-rendering patterns that eliminate it.","md",{"slug":1183,"type":1184,"breadcrumb":1185,"datePublished":1193,"dateModified":1193},"fixing-cls-from-cookie-consent-banners","article",[1186,1189,1190,1191],{"name":1187,"url":1188},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1192},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners",{"title":5,"description":1197},"Consent banners injected at the top of the page push content down and fail CLS. Overlay them, reserve space, or server-render them to keep CLS under 0.1.","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-cookie-consent-banners\u002Findex","bZ86XvXvU4yXVJ1pb2ttxjJ7YcQFQX8NzkDIqQvSWQs",[1201,1205],{"title":1202,"path":1203,"stem":1204},"Fixing CLS from Animating Layout Properties","\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-animating-layout-properties","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-animating-layout-properties\u002Findex",{"title":1206,"path":1207,"stem":1208},"Fixing CLS from Skeleton Screens That Resize","\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-skeleton-screens-that-resize","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-skeleton-screens-that-resize\u002Findex",1791308078755]