Skip to content

Language

Engineering5 min read

Cookie banners and Core Web Vitals: keeping LCP, CLS and INP green

How cookie banners hurt LCP, CLS and INP, and how to fix it: load consent scripts async, avoid layout shift, defer the preference center and yield in handlers.

Cookiesh Editorial TeamPublished

A cookie banner can hurt all three Core Web Vitals: it can delay or become the Largest Contentful Paint (LCP), push content around and raise Cumulative Layout Shift (CLS), and make the Accept button feel sluggish, which shows up in Interaction to Next Paint (INP). The fixes are known: load the consent script asynchronously, render the banner as a fixed overlay, defer secondary UI and keep click handlers light.

A banner is often the first thing a new visitor sees, and it is built from JavaScript, CSS and text that compete with your real content for bandwidth and main-thread time. Core Web Vitals measure exactly that competition, assessed at the 75th percentile of real page loads.

Metric What it measures “Good” threshold How a banner can hurt it
LCP When the largest image or text block renders 2.5 s or less A render-blocking consent script, or the banner itself becoming the largest element
CLS How much visible content moves unexpectedly 0.1 or less A banner inserted into the page flow that pushes content down
INP How quickly the page responds to clicks, taps and key presses 200 ms or less Heavy work in the Accept or Reject handler

INP replaced First Input Delay (FID) in March 2024, which matters for consent. FID only measured the delay before the first interaction was handled; INP measures the full time from any interaction to the next frame, and “Accept all” is often the first thing a visitor clicks.

Load it with async and keep it small. A synchronous script in the <head> stops the HTML parser until it has downloaded and run, which delays your headline and hero image along with everything else.

<head>
  <script src="https://consent.example.com/loader.js" async></script>

  <!-- Inert until the consent script activates it after an opt-in -->
  <script type="text/plain" data-consent="analytics"
          src="https://analytics.example.com/script.js"></script>
</head>

Async loading is safe when non-essential tags are inert like this: nothing runs until the consent script allows it. “Auto-blocking” approaches that intercept scripts as they are added usually have to run synchronously before any other script, which costs render time. Our guide to blocking scripts until consent compares the methods.

The second LCP trap is the banner itself. LCP reports the largest image or text block painted in the viewport before the visitor interacts. On a small phone, a banner with a long paragraph can be bigger than your headline, and if it paints late, your LCP is late. Keep the first layer’s text concise, use a system font so it never waits for a web font, and never ship the banner as an image.

Delaying the banner does not help: a late banner can still become the LCP element, just later.

How do I stop the banner from causing layout shift?

Render it as a fixed overlay. CLS counts movement of existing content, so an element with position: fixed that appears on top of the page does not shift anything. The classic mistake is a bar inserted at the top of <body> after first paint, which pushes the whole page down.

.consent-banner {
  position: fixed;
  inset: auto 0 0 0; /* pinned to the bottom, outside the document flow */
  z-index: 1000;
  font: 1rem/1.5 system-ui, sans-serif; /* no web font to wait for */
  animation: consent-in 200ms ease-out;
}

@keyframes consent-in {
  from { transform: translateY(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .consent-banner { animation: none; }
}

If you animate, animate transform or opacity. Animating top, height or margin changes layout and can register as shifts. If your design truly needs an in-flow banner, reserve its space in the server-rendered HTML, which only works well if the server reads the consent cookie and skips the space for returning visitors.

Also plan for what happens after consent. Shifts within 500 ms of a click or key press are excluded from CLS, but an ad slot, video embed or chat widget that loads a few seconds later is not. Give those containers fixed dimensions or an aspect-ratio.

How do I defer banner UI without hurting compliance?

Ship the first layer up front and load the preference center on demand. The first layer is a short message and a few buttons; the second layer, with category toggles, vendor lists and cookie tables, is usually the heavy part.

What you must not defer is choice. “Reject all” belongs in the first layer with the same prominence as “Accept all”. In December 2021, France’s CNIL fined Google €150 million and Facebook €60 million because refusing cookies was harder than accepting them. See cookie banner dark patterns and our banner design best practices.

How do I keep Accept and Reject responsive for INP?

Do the minimum before the next paint, then yield. A slow handler typically writes the consent cookie, sends a consent log, updates Google Consent Mode and injects several vendor scripts in one long task, so the button looks frozen.

function yieldToMain() {
  if (globalThis.scheduler?.yield) return globalThis.scheduler.yield();
  return new Promise((resolve) => setTimeout(resolve, 0));
}

acceptButton.addEventListener('click', async () => {
  banner.remove();                                    // 1. visible feedback first
  saveConsent({ analytics: true, marketing: true });  // 2. small and synchronous
  await yieldToMain();                                // 3. let the browser paint
  activateTags(['analytics', 'marketing']);           // 4. heavy work in a later task
});

customizeButton.addEventListener('click', async () => {
  customizeButton.setAttribute('aria-busy', 'true');
  const { openPreferences } = await import('./preference-center.js'); // loaded on demand
  openPreferences();
});

Send the consent log with navigator.sendBeacon() so network work never holds up the interaction, and activate many tags in batches with a yield between each. scheduler.yield() is not in every browser yet, hence the fallback.

How should I test a banner’s impact?

Test as a first-time visitor, then as a returning one. Lab tools like Lighthouse start with empty storage, so they always see the banner:

  1. Open the page in a fresh browser profile from a region where the banner appears.
  2. Run Lighthouse or PageSpeed Insights and check which element is reported as the LCP element.
  3. Record a trace in the Chrome DevTools Performance panel, click “Accept all”, and look for long tasks after the click.
  4. Check the same trace for layout shifts while the banner appears and after tags load.
  5. Repeat as a returning visitor with consent stored, since more tags run.
  6. Compare field data in Search Console or PageSpeed Insights afterwards; it covers a rolling 28-day window, so changes show up gradually.

Bottom line

A consent banner does not have to cost you Core Web Vitals. Load it async, keep it out of the document flow, defer the heavy second layer and keep click handlers light, while leaving every choice equally easy to make. Tools like Cookiesh can handle the loading side, with an async loader under 5 kB gzipped, served from a global edge CDN and built to avoid layout shift.

This article is general information, not legal advice. For decisions about your specific situation, consult a qualified privacy professional.

Frequently asked questions

Does hiding the banner from Lighthouse improve my Core Web Vitals?

No. Google's Core Web Vitals assessment is based on field data from real Chrome users, many of whom see your banner. Hiding it from lab tools only hides the problem from you, and serving test tools a different page makes your diagnostics unreliable.

Is a bottom bar or a center modal better for performance?

Both avoid layout shift when they use fixed positioning. A large modal covers more of the viewport and is more likely to be reported as the LCP element on mobile, so a compact bottom bar is usually the safer choice for performance, as long as it keeps every choice equally prominent.

Should I measure Core Web Vitals separately for visitors who already consented?

It helps. Returning visitors skip the banner but run more tags, so their loading and responsiveness profile differs from first-time visitors. Segmenting your measurements tells you which group needs work, but remember that sending real-user monitoring data to a third party may itself need consent.

Sources

  1. web.dev: Web Vitals
  2. Directive 2002/58/EC (ePrivacy Directive), EUR-Lex
  3. EDPB Guidelines 05/2020 on consent

Cookiesh Editorial Team

Privacy engineers and compliance specialists writing practical guidance on consent, cookies and data protection for product teams.

Ship compliant cookie banners in minutes, not sprints

Start free on one domain. Upgrade when you need more domains, geo-targeting or custom branding.

No credit card required · Cancel anytime