Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Show a cookie consent notice

Websites that set non-essential cookies must obtain prior, informed user consent under GDPR, CCPA, and similar privacy regulations before cookies are placed.

Utilities
Quick take
Typical fix time 30 min
  • GDPR requires consent before setting non-essential cookies (analytics, advertising, personalization)
  • Essential/strictly necessary cookies (session, security, login) do not require consent
  • Consent must be freely given, specific, informed, and unambiguous — pre-ticked boxes are invalid
  • Users must be able to withdraw consent as easily as they gave it
  • Scripts and pixels must not load until the user has actively accepted their category
  • Non-essential trackers must stay blocked until consent and after revocation
Why it matters: GDPR violations can result in fines up to €20 million or 4% of global annual turnover. Regulators across the EU have actively fined organizations for deploying tracking cookies without valid consent.

Rule Details

The EU's General Data Protection Regulation (GDPR) and ePrivacy Directive require websites to obtain informed consent before placing non-essential cookies on a user's device. Similar requirements exist in CCPA (California), LGPD (Brazil), PIPEDA (Canada), and PECR (UK).

Code Examples

Recommended CMPs that handle GDPR compliance:

  • Cookiebot — automated cookie scanning + consent management
  • OneTrust — enterprise-grade compliance
  • CookieYes — small-to-medium sites
  • Osano — open source friendly

Manual Implementation Pattern

<!-- Do NOT load analytics before consent -->
<!-- ❌ Wrong: loads before consent -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"></script>
 
<!-- ✅ Correct: analytics loaded only after consent -->
<script>
  // Check consent before loading analytics
  if (getConsentStatus('analytics') === 'granted') {
    loadGoogleAnalytics()
  }
</script>
// consent.ts
const CONSENT_KEY = 'cookie-consent'
const CONSENT_VERSION = 'v2' // Bump when purposes change
 
interface ConsentPreferences {
  version: string
  analytics: boolean
  advertising: boolean
  functional: boolean
  timestamp: number
}
 
export function getConsent(): ConsentPreferences | null {
  try {
    const stored = localStorage.getItem(CONSENT_KEY)
    if (!stored) return null
    const parsed = JSON.parse(stored) as ConsentPreferences
    // Invalidate old consent versions
    if (parsed.version !== CONSENT_VERSION) return null
    return parsed
  } catch {
    return null
  }
}
 
export function setConsent(preferences: Omit<ConsentPreferences, 'version' | 'timestamp'>) {
  const consent: ConsentPreferences = {
    ...preferences,
    version: CONSENT_VERSION,
    timestamp: Date.now(),
  }
  localStorage.setItem(CONSENT_KEY, JSON.stringify(consent))
  applyConsentDecision(consent)
}
 
function applyConsentDecision(consent: ConsentPreferences) {
  if (consent.analytics) {
    loadGoogleAnalytics()
  } else {
    disableAnalyticsCookies()
  }
  if (consent.advertising) {
    loadAdvertisingPixels()
  } else {
    disableAdvertisingCookies()
  }
}
 
function loadGoogleAnalytics() {
  const script = document.createElement('script')
  script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXX'
  script.async = true
  document.head.appendChild(script)
}
'use client'
 
import { useState, useEffect } from 'react'
import { getConsent, setConsent } from './consent'
 
export function CookieConsentBanner() {
  const [visible, setVisible] = useState(false)
  const [showDetails, setShowDetails] = useState(false)
  const [analytics, setAnalytics] = useState(false)
  const [advertising, setAdvertising] = useState(false)
 
  useEffect(() => {
    // Show banner only if no consent has been recorded
    if (!getConsent()) {
      setVisible(true)
    }
  }, [])
 
  const acceptAll = () => {
    setConsent({ analytics: true, advertising: true, functional: true })
    setVisible(false)
  }
 
  const rejectAll = () => {
    setConsent({ analytics: false, advertising: false, functional: false })
    setVisible(false)
  }
 
  const savePreferences = () => {
    setConsent({ analytics, advertising, functional: true })
    setVisible(false)
  }
 
  if (!visible) return null
 
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="consent-title"
      className="cookie-consent-banner"
    >
      <h2 id="consent-title">We use cookies</h2>
      <p>
        We use cookies to improve your experience. Some are essential; others
        help us understand how you use our site.{' '}
        <a href="/privacy">Privacy Policy</a>
      </p>
 
      {showDetails && (
        <div className="consent-details">
          <label>
            <input type="checkbox" checked disabled readOnly />
            <strong>Strictly necessary</strong> — required for the site to work
          </label>
          <label>
            <input
              type="checkbox"
              checked={analytics}
              onChange={(e) => setAnalytics(e.target.checked)}
            />
            <strong>Analytics</strong> — helps us understand usage patterns
          </label>
          <label>
            <input
              type="checkbox"
              checked={advertising}
              onChange={(e) => setAdvertising(e.target.checked)}
            />
            <strong>Advertising</strong> — personalised ads
          </label>
        </div>
      )}
 
      <div className="consent-actions">
        <button onClick={rejectAll}>Reject non-essential</button>
        <button onClick={() => setShowDetails(!showDetails)}>
          {showDetails ? 'Hide' : 'Manage preferences'}
        </button>
        {showDetails && (
          <button onClick={savePreferences}>Save preferences</button>
        )}
        <button onClick={acceptAll} className="primary">
          Accept all
        </button>
      </div>
    </div>
  )
}

Why It Matters

GDPR violations can result in fines up to €20 million or 4% of global annual turnover. Regulators across the EU have actively fined organizations for deploying tracking cookies without valid consent.

CategoryExamplesConsent Required
Strictly necessarySession cookies, CSRF tokens, login stateNo
FunctionalLanguage preference, accessibility settingsDepends — essential if core to service
AnalyticsGoogle Analytics, MixpanelYes
AdvertisingGoogle Ads, Facebook Pixel, remarketingYes
Social mediaTwitter/LinkedIn share buttons, embedsYes

The GDPR Article 4(11) definition of consent requires that it be:

  • Freely given — declining must be as easy as accepting; no cookie wall blocking access
  • Specific — separate consent for each purpose (analytics vs. advertising)
  • Informed — the user understands what they are consenting to
  • Unambiguous — requires a clear affirmative action; pre-ticked boxes are not valid

Users must also be able to revisit and change that choice later from a stable entry point such as the footer, account settings, or privacy center.

For sites using Google Analytics or Google Ads, implement Google Consent Mode to signal consent state:

// Initialize consent mode BEFORE loading GTM or GA
window.dataLayer = window.dataLayer || []
function gtag() { dataLayer.push(arguments) }
 
// Set default state — deny all until consent is given
gtag('consent', 'default', {
  analytics_storage: 'denied',
  ad_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
  wait_for_update: 500, // milliseconds to wait for consent update
})
// After user grants consent
gtag('consent', 'update', {
  analytics_storage: userConsent.analytics ? 'granted' : 'denied',
  ad_storage: userConsent.advertising ? 'granted' : 'denied',
})
Consent Banners Must Block Scripts, Not Just Show a Notice

Many sites display a cookie banner but load analytics and advertising scripts in the page regardless of user choice. This is not GDPR-compliant. Scripts must not execute until after the user has actively accepted their category.

Standards

  • Use these references as the standard for the legal or product-facing privacy behavior that users actually experience.
  • Check the implementation against GDPR Article 6 - Lawfulness of processing before treating the rule as satisfied.
  • Check the implementation against GDPR Recital 32 - Consent before treating the rule as satisfied.

Verification

Automated Checks

  • Test the affected flow in a production-like environment, not just local development.
  • Document any intentional exceptions explicitly.

Manual Checks

  • Inspect the final HTTP response or browser behavior to confirm the control is actually enforced.
  • Verify third-party integrations or embeds still work after the restriction is applied.
  • Accept analytics, then revoke it, and confirm the tracker stops initializing on the next page load.
  • Confirm a visible "Cookie settings" or equivalent control remains available after the banner is dismissed.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Check whether the site displays a cookie consent notice before setting non-essential cookies. Verify that analytics, advertising, and tracking scripts do not load until the user has actively accepted. Check whether users can reject non-essential cookies without losing access to content.

Fix

Auto-fix issues

Implement a cookie consent management platform (CMP) that blocks non-essential scripts until consent is granted. Configure analytics and tracking tags to initialize only after explicit consent. Provide a mechanism for users to change their consent preferences at any time.

Explain

Learn more

Explain what GDPR requires for cookie consent, the difference between essential and non-essential cookies, why pre-ticked boxes are invalid consent, and what a technically compliant consent implementation looks like.

Review

Code review

Review server config, headers, forms, and integration points related to Show a cookie consent notice. Flag exact responses, cookies, or browser behaviors that violate the rule, and verify them against the effective production-like response. Check that tracker initialization is gated on consent and that a persistent "change cookie settings" path exists after the banner is gone.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Mozilla Observatory
observatory.mozilla.orgTool

Rules that often go hand-in-hand with this one.

Avoid third-party cookies

Third-party cookies set by external domains track users across sites without their knowledge. Modern browsers are phasing them out, and regulations like GDPR and CCPA require consent before setting them.

Privacy
Link to your privacy policy in the footer

Websites that collect any personal data must publish a privacy policy and link to it prominently — this is a legal requirement under GDPR, CCPA, and most other privacy regulations.

Privacy
Avoid intrusive interstitials

Full-screen interstitials (pop-ups, overlays, cookie banners) that block the main content on mobile are a ranking penalty signal and accessibility barrier. Use non-intrusive alternatives.

CSS
Load non-critical code on user interaction

Defer JavaScript modules, widgets, and third-party code until the user signals intent through a click, focus, hover, or similar interaction.

Performance

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385