Implementing a GDPR-compliant cookie consent banner requires more than showing a popup and hoping for the best. The ePrivacy Directive and GDPR together demand that you obtain meaningful consent before setting non-essential cookies. This guide covers practical implementation patterns that work in 2026, with code examples developers can adapt.
Consent Requirements Under GDPR
The GDPR requires that consent be freely given, specific, informed, and unambiguous. For cookie consent, this means users must make an affirmative action—clicking “Accept,” toggling switches, or similar clear actions. Pre-ticked boxes, implied continued browsing, and soft opt-ins do not satisfy the standard.
The banner must also allow users to withdraw consent as easily as they gave it. This means your site needs a persistent way to access consent preferences after the initial choice.
Building the Consent Manager
A practical cookie consent system consists of three parts: the UI component, a consent state store, and the blocking logic that prevents cookies until consent is granted.
The Consent Store
Store consent state in localStorage with a clear structure that tracks what was consented to and when:
// consent-manager.js
const CONSENT_KEY = 'gdpr_consent_preferences';
export function getConsentState() {
const stored = localStorage.getItem(CONSENT_KEY);
if (!stored) return null;
try {
return JSON.parse(stored);
} catch (e) {
return null;
}
}
export function saveConsentState(consent) {
const state = {
necessary: true, // Always required
analytics: consent.analytics || false,
marketing: consent.marketing || false,
timestamp: new Date().toISOString(),
version: '1.0'
};
localStorage.setItem(CONSENT_KEY, JSON.stringify(state));
return state;
}
export function hasConsent(category) {
const state = getConsentState();
return state ? state[category] : false;
}
This approach keeps consent preferences available across sessions while remaining transparent about what was saved.
Banner HTML Structure
The banner markup should be accessible and provide clear options:
<div id="cookie-banner" class="cookie-banner" role="dialog" aria-label="Cookie consent">
<div class="cookie-content">
<h2>We use cookies</h2>
<p>We use necessary cookies for basic functionality. With your consent, we may also set analytics and marketing cookies to improve your experience.</p>
<div class="cookie-options">
<label class="cookie-toggle">
<input type="checkbox" checked disabled>
<span>Necessary</span>
<small>Required for basic site functionality</small>
</label>
<label class="cookie-toggle">
<input type="checkbox" id="consent-analytics">
<span>Analytics</span>
<small>Help us understand how visitors use our site</small>
</label>
<label class="cookie-toggle">
<input type="checkbox" id="consent-marketing">
<span>Marketing</span>
<small>Used for personalized content and ads</small>
</label>
</div>
<div class="cookie-actions">
<button id="btn-reject-all" class="btn btn-secondary">Reject All</button>
<button id="btn-save-preferences" class="btn btn-secondary">Save Preferences</button>
<button id="btn-accept-all" class="btn btn-primary">Accept All</button>
</div>
</div>
</div>
Style the banner to appear at the bottom of the viewport with a high z-index to ensure visibility without completely blocking content access.
Consent Event Handlers
Wire up the buttons to save appropriate consent states:
import { saveConsentState, getConsentState } from './consent-manager.js';
document.addEventListener('DOMContentLoaded', () => {
const banner = document.getElementById('cookie-banner');
// Don't show banner if consent already exists
if (getConsentState()) {
banner.style.display = 'none';
initializeScripts();
return;
}
document.getElementById('btn-accept-all').addEventListener('click', () => {
saveConsentState({ analytics: true, marketing: true });
banner.style.display = 'none';
initializeScripts();
});
document.getElementById('btn-reject-all').addEventListener('click', () => {
saveConsentState({ analytics: false, marketing: false });
banner.style.display = 'none';
initializeScripts();
});
document.getElementById('btn-save-preferences').addEventListener('click', () => {
const analytics = document.getElementById('consent-analytics').checked;
const marketing = document.getElementById('consent-marketing').checked;
saveConsentState({ analytics, marketing });
banner.style.display = 'none';
initializeScripts();
});
});
Blocking Scripts Until Consent
The critical part of compliance is preventing cookies from being set before consent is obtained. The easiest approach is to modify how third-party scripts load.
Script Tag Wrapper
Create a wrapper function that only loads scripts when consent exists:
function loadScriptIfConsented(category, src, callback) {
const { hasConsent } = require('./consent-manager.js');
if (!hasConsent(category)) {
console.log(`Script blocked: ${src} (no consent for ${category})`);
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
if (callback) {
script.onload = callback;
}
document.head.appendChild(script);
}
// Usage - only load analytics after consent
loadScriptIfConsented('analytics', 'https://www.googletagmanager.com/gtag/js?id=UA-XXXXX');
loadScriptIfConsented('marketing', 'https://connect.facebook.net/en_US/fbevents.js');
For Google Analytics specifically, wrap the initialization:
function initAnalytics() {
const { hasConsent } = require('./consent-manager.js');
if (!hasConsent('analytics')) {
// Disable Google Analytics tracking
window['ga-disable-UA-XXXXX'] = true;
return;
}
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-XXXXX');
}
Providing Access to Consent Preferences
GDPR requires users to be able to withdraw consent. Add a link in your footer that reopens the consent UI:
<a href="#" id="manage-cookies">Manage Cookie Preferences</a>
document.getElementById('manage-cookies').addEventListener('click', (e) => {
e.preventDefault();
const banner = document.getElementById('cookie-banner');
const state = getConsentState();
// Pre-check checkboxes based on current consent
document.getElementById('consent-analytics').checked = state?.analytics || false;
document.getElementById('consent-marketing').checked = state?.marketing || false;
banner.style.display = 'block';
});
When users save new preferences, reload the page or reinitialize scripts to apply the changes immediately.
Handling Consent Receipts
For compliance documentation, store consent events on your server when users create accounts or update preferences:
async function recordConsent(consentState) {
if (!window.userId) return; // Only for logged-in users
await fetch('/api/consent-record', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: window.userId,
consent: consentState,
ipAddress: '', // Server logs this
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
})
});
}
This provides an audit trail if questions arise about when and what consent was obtained.
Testing Your Implementation
Verify your consent system works correctly:
- Clear all storage and reload—the banner should appear
- Click each button and verify the correct consent state saves
- Check network tab—no analytics or marketing cookies should appear before consent
- Test preference management—changes should apply immediately
- Verify across devices—consent should persist correctly
Use browser DevTools to inspect localStorage and confirm the consent JSON matches expectations.
Summary
A compliant cookie consent implementation requires explicit consent via affirmative actions, script blocking until consent is granted, the ability to withdraw and modify choices, and documentation of consent events. The patterns shown here provide a foundation that satisfies 2026 requirements while remaining maintainable for development teams.
Related Reading
Built by theluckystrike — More at zovo.one