cookie-consent-gdpr
v1.0.0
Published
A lightweight, fully GDPR-compliant cookie consent banner with granular category control, preference management, and automatic script blocking.
Maintainers
Readme
GDPR Cookie Consent
A lightweight (~9.7 KB gzipped), fully GDPR & ePrivacy Directive compliant cookie consent banner. Zero dependencies. Framework-agnostic. Production-ready.
Provides granular cookie category control, a preferences modal with full cookie disclosure, automatic script blocking, consent record keeping, and optional server-side webhook — everything you need to comply with EU privacy regulations without paying for a SaaS tool.
Table of Contents
- Features
- Quick Start
- Installation
- Configuration
- Layout Modes
- HTML Auto-Initialization
- Script & Element Blocking
- JavaScript API
- Events
- Preferences Button
- TypeScript Support
- Accessibility
- GDPR Compliance Reference
- Browser Support
- License
Features
- Three layout modes — bottom/top bar, centered modal, corner popup
- Granular consent — per-category control (Necessary, Functional, Analytics, Marketing — or define your own)
- Full cookie disclosure — accordion UI showing every cookie's name, provider, purpose, expiry, and type
- Automatic script blocking — scripts, iframes, and images are blocked until the user consents to the relevant category
- Consent records — every consent decision is stored with a UUID, timestamp, categories, URL, and user agent
- Webhook support — optionally POST consent records to your server for GDPR Article 7(1) proof-of-consent
- Config versioning — when you change your cookie configuration, users are automatically re-prompted
- Fully customizable — every text string, color, font, and size is configurable
- Accessible — WCAG 2.1 AA compliant: focus trapping, keyboard navigation, ARIA attributes, reduced-motion support
- Zero dependencies — pure vanilla JavaScript, works with any framework or static site
- Tiny footprint — ~9.7 KB gzipped, ~38 KB minified
- Multiple formats — ESM, CJS, UMD, and standalone IIFE (script tag)
- TypeScript declarations included
- Cross-subdomain support — share consent across subdomains via cookie domain config
- No pre-ticked boxes — GDPR Recital 32 compliant out of the box
- Equal reject/accept prominence — reject button is always visible alongside accept
Quick Start
Script Tag (Fastest)
<script src="https://unpkg.com/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>
<script>
CookieConsent.init({
categories: {
necessary: {
enabled: true,
readOnly: true,
title: 'Strictly Necessary',
description: 'Essential cookies that make the website work.',
cookies: [
{
name: 'session_id',
provider: 'yourdomain.com',
purpose: 'Maintains your session across page requests.',
expiry: 'Session',
type: 'HTTP Cookie',
},
],
},
analytics: {
enabled: false,
readOnly: false,
title: 'Analytics',
description: 'Help us understand how visitors interact with our website.',
cookies: [
{
name: '_ga',
provider: 'Google Analytics',
purpose: 'Distinguishes unique users by assigning a randomly generated number.',
expiry: '2 years',
type: 'HTTP Cookie',
},
{
name: '_ga_*',
provider: 'Google Analytics',
purpose: 'Used to persist session state.',
expiry: '2 years',
type: 'HTTP Cookie',
},
],
},
marketing: {
enabled: false,
readOnly: false,
title: 'Marketing',
description: 'Used to deliver personalised advertisements.',
cookies: [
{
name: '_fbp',
provider: 'Facebook',
purpose: 'Tracks visits across websites for targeted advertising.',
expiry: '3 months',
type: 'HTTP Cookie',
},
],
},
},
privacyPolicyUrl: '/privacy-policy',
});
</script>That's it. The banner appears, users make their choice, scripts are blocked/unblocked accordingly, and consent is stored.
Installation
NPM / Yarn
npm install cookie-consent-gdpr
# or
yarn add cookie-consent-gdprimport CookieConsent from 'cookie-consent-gdpr';
CookieConsent.init({
// your config
});CDN (Script Tag)
<!-- unpkg -->
<script src="https://unpkg.com/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>
<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>Self-Hosted
Download dist/cookie-consent-gdpr.min.js from this repository and include it:
<script src="/path/to/cookie-consent-gdpr.min.js"></script>Configuration
Full Configuration Reference
CookieConsent.init({
// ─── Container ────────────────────────────────────────────────
// CSS selector or DOM element where the banner mounts.
// If null, a container is auto-created and appended to document.body.
container: null,
// ─── Layout ───────────────────────────────────────────────────
// 'bar' | 'modal' | 'popup'
layout: 'bar',
// 'bottom' | 'top' (for bar)
// 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' (for popup)
position: 'bottom',
// ─── Behavior ─────────────────────────────────────────────────
autoShow: true, // Show banner on page load if no consent exists
reconsentOnChange: true, // Re-prompt when cookie config changes
forceOverlay: false, // Block page interaction until consent is given
closeOnBackdrop: false, // Clicking backdrop closes banner (false = strict GDPR)
// ─── Preferences Button ───────────────────────────────────────
// Selector or element. Also auto-binds [data-cc-open-preferences].
preferencesButton: null,
// ─── Privacy Policy ───────────────────────────────────────────
privacyPolicyUrl: '',
// ─── Cookie Storage ───────────────────────────────────────────
cookie: {
name: 'cc_consent', // Consent cookie name
domain: '', // '' = current domain. '.example.com' = all subdomains
path: '/',
expiryDays: 365, // GDPR recommends ≤12 months
sameSite: 'Lax', // 'Lax' | 'Strict' | 'None'
secure: true, // Auto-detected from protocol by default
},
// ─── Webhook ──────────────────────────────────────────────────
webhook: {
url: null, // POST endpoint for consent records. null = disabled.
headers: {}, // Additional headers (e.g. authorization)
},
// ─── Categories ───────────────────────────────────────────────
categories: {
necessary: {
enabled: true,
readOnly: true,
title: 'Strictly Necessary',
description: '...',
cookies: [],
},
functional: {
enabled: false,
readOnly: false,
title: 'Functional',
description: '...',
cookies: [],
},
analytics: {
enabled: false,
readOnly: false,
title: 'Analytics',
description: '...',
cookies: [],
},
marketing: {
enabled: false,
readOnly: false,
title: 'Marketing',
description: '...',
cookies: [],
},
},
// ─── Texts (i18n) ────────────────────────────────────────────
texts: { /* see Texts section below */ },
// ─── Theme ────────────────────────────────────────────────────
theme: { /* see Theme section below */ },
// ─── Callbacks ────────────────────────────────────────────────
onConsent: function (consent) {},
onRevoke: function (consent) {},
onAccept: function (category) {},
onReject: function (category) {},
});Cookie Categories
Define as many categories as you need. Each category requires:
| Property | Type | Description |
| ------------- | ---------- | -------------------------------------------------------- |
| enabled | boolean | Default state. Must be false for non-necessary categories (GDPR Recital 32). |
| readOnly | boolean | If true, the user cannot toggle this off. Use for strictly necessary cookies. |
| title | string | Display title shown in the preferences modal. |
| description | string | Explains what this category is for. |
| cookies | Array | List of cookie detail objects (see below). |
Standard categories: necessary, functional, analytics, marketing. You can add custom ones:
categories: {
necessary: { enabled: true, readOnly: true, title: 'Essential', ... },
social_media: {
enabled: false,
readOnly: false,
title: 'Social Media',
description: 'Cookies used by social media embeds and sharing widgets.',
cookies: [
{ name: '__stripe_mid', provider: 'Stripe', purpose: 'Fraud prevention', expiry: '1 year', type: 'HTTP Cookie' }
],
},
},Cookie Details
Each entry in a category's cookies array should provide the information required by GDPR Article 13:
| Property | Type | Description |
| ---------- | -------- | -------------------------------------------------- |
| name | string | Cookie name or wildcard pattern (e.g. _ga_*) |
| provider | string | Who sets this cookie (domain or company name) |
| purpose | string | Human-readable explanation of what it does |
| expiry | string | Duration (e.g. 1 year, Session, 30 days) |
| type | string | HTTP Cookie, localStorage, sessionStorage, Pixel |
All cookie details are displayed in the preferences modal accordion — this is required by the ePrivacy Directive.
Texts / i18n
Override any text string to support your language:
texts: {
bannerTitle: 'We use cookies',
bannerDescription: 'We use cookies to improve your experience...',
acceptAll: 'Accept All',
rejectAll: 'Reject All',
settings: 'Cookie Settings',
preferencesTitle: 'Cookie Preferences',
preferencesDescription: 'Choose which cookies you want to allow...',
save: 'Save Preferences',
acceptAllPreferences: 'Accept All',
rejectAllPreferences: 'Reject All',
alwaysActive: 'Always Active',
cookieNameLabel: 'Name',
cookieProviderLabel: 'Provider',
cookiePurposeLabel: 'Purpose',
cookieExpiryLabel: 'Expiry',
cookieTypeLabel: 'Type',
noCookies: 'No cookies to display.',
privacyPolicyLabel: 'Privacy Policy',
poweredBy: '', // Optional branding text
}Example: German
texts: {
bannerTitle: 'Wir verwenden Cookies',
bannerDescription: 'Wir verwenden Cookies und ähnliche Technologien...',
acceptAll: 'Alle akzeptieren',
rejectAll: 'Alle ablehnen',
settings: 'Cookie-Einstellungen',
preferencesTitle: 'Cookie-Einstellungen',
preferencesDescription: 'Hier können Sie Ihre Cookie-Präferenzen verwalten...',
save: 'Einstellungen speichern',
acceptAllPreferences: 'Alle akzeptieren',
rejectAllPreferences: 'Alle ablehnen',
alwaysActive: 'Immer aktiv',
}Theme
Customize every visual aspect:
theme: {
primary: '#0e6b4e', // Primary button & accent color
primaryHover: '#0a5a40', // Primary button hover state
primaryText: '#ffffff', // Text on primary buttons
background: '#ffffff', // Banner/modal background
text: '#333333', // Primary text color
textSecondary: '#666666', // Secondary/description text
border: '#e0e0e0', // Borders and dividers
overlay: 'rgba(0,0,0,0.55)', // Backdrop overlay color
toggleOn: '#0e6b4e', // Toggle switch ON color
toggleOff: '#cccccc', // Toggle switch OFF color
toggleKnob: '#ffffff', // Toggle knob color
fontFamily: "'Inter', sans-serif",
fontSize: '14px',
borderRadius: '8px',
zIndex: 2147483645, // Ensures banner is above everything
maxWidth: '1140px', // Max width for bar layout
popupWidth: '400px', // Width for popup layout
}You can also override theme values directly in CSS using custom properties:
.cc-container {
--cc-primary: #ff6600;
--cc-bg: #1a1a2e;
--cc-text: #eaeaea;
}Webhook (Consent Records)
GDPR Article 7(1) requires you to be able to demonstrate that the user gave consent. While the consent cookie provides client-side proof, sending records to your server is recommended.
webhook: {
url: 'https://api.yoursite.com/consent',
headers: {
'Authorization': 'Bearer your-api-key',
},
}When consent is given or updated, a POST request is sent with this payload:
{
"consentId": "a1b2c3d4-e5f6-...",
"timestamp": "2026-02-23T14:30:00.000Z",
"categories": {
"necessary": true,
"functional": false,
"analytics": true,
"marketing": false
},
"url": "https://yoursite.com/page",
"userAgent": "Mozilla/5.0 ...",
"configHash": "k8m3x"
}Cross-Domain / Subdomain Cookies
To share consent across subdomains (e.g. www.example.com and app.example.com):
cookie: {
domain: '.example.com', // Note the leading dot
}This sets the consent cookie on the parent domain, making it accessible to all subdomains.
For true cross-origin consent (different TLDs), you would need a server-side approach using the webhook to synchronize consent records.
Layout Modes
Bottom Bar (default)
CookieConsent.init({
layout: 'bar',
position: 'bottom', // or 'top'
});A full-width bar fixed to the bottom (or top) of the viewport. Most common pattern.
Center Modal
CookieConsent.init({
layout: 'modal',
forceOverlay: true, // recommended for modal
});A centered dialog with backdrop. Good for high-consent-rate scenarios.
Corner Popup
CookieConsent.init({
layout: 'popup',
position: 'bottom-right', // or 'bottom-left', 'top-right', 'top-left'
});A small card positioned in the corner. Less intrusive.
HTML Auto-Initialization
For users with minimal JavaScript knowledge, the banner can auto-initialize using HTML attributes:
Option 1: Script attribute
<script
src="cookie-consent-gdpr.min.js"
data-cc-auto
data-cc-config='{"privacyPolicyUrl": "/privacy", "categories": { ... }}'
></script>Option 2: Container element
<div id="cc-banner" data-cc-auto data-cc-config='{"layout": "popup"}'></div>
<script src="cookie-consent-gdpr.min.js"></script>The data-cc-config attribute accepts a JSON string with any configuration options. If omitted, default settings are used.
Script & Element Blocking
The most important GDPR requirement: no non-essential cookies may be set before consent is given. This library provides automatic blocking.
Blocking Scripts
Change type="text/javascript" to type="text/plain" and add a data-cookiecategory attribute:
<!-- This script will NOT execute until the user consents to "analytics" -->
<script
type="text/plain"
data-cookiecategory="analytics"
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"
></script>
<!-- Inline scripts work too -->
<script type="text/plain" data-cookiecategory="analytics">
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXXXXX');
</script>Blocking Iframes
Use data-src instead of src:
<!-- YouTube embed blocked until marketing consent -->
<iframe
data-cookiecategory="marketing"
data-src="https://www.youtube.com/embed/dQw4w9WgXcQ"
src="about:blank"
width="560"
height="315"
></iframe>Blocking Images (Tracking Pixels)
<img
data-cookiecategory="analytics"
data-src="https://analytics.example.com/pixel.gif"
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
/>When the user consents to a category, all matching elements are automatically activated: scripts are cloned with type="text/javascript", and data-src is copied to src.
A MutationObserver also watches for dynamically added elements, so scripts injected after page load are handled too.
JavaScript API
All methods return the CookieConsent object for chaining (except getters).
| Method | Description |
| ------------------------------- | -------------------------------------------------- |
| CookieConsent.init(config) | Initialize with configuration. |
| CookieConsent.show() | Show the consent banner. |
| CookieConsent.hide() | Hide the consent banner. |
| CookieConsent.showPreferences() | Open the preferences/settings modal. |
| CookieConsent.hidePreferences() | Close the preferences modal. |
| CookieConsent.acceptAll() | Accept all cookie categories. |
| CookieConsent.rejectAll() | Reject all non-essential categories. |
| CookieConsent.acceptCategory(key) | Accept a specific category by key. |
| CookieConsent.getConsent() | Get the full consent record (or null). |
| CookieConsent.hasConsented() | true if the user has made any consent decision. |
| CookieConsent.hasCategory(key) | true if a specific category is consented. |
| CookieConsent.revokeConsent() | Revoke consent, delete cookie, re-show banner. |
| CookieConsent.on(event, fn) | Register an event listener. |
| CookieConsent.off(event, fn) | Remove an event listener. |
| CookieConsent.destroy() | Fully remove UI and clean up all listeners. |
| CookieConsent.getConfig() | Get a read-only copy of the active configuration. |
Example: Conditional Loading
CookieConsent.init({
onConsent: function (consent) {
if (consent.categories.analytics) {
// Load Google Analytics dynamically
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXX';
document.head.appendChild(script);
}
},
});Example: Check Consent Before Action
if (CookieConsent.hasCategory('marketing')) {
// Safe to initialize Facebook Pixel
fbq('init', '1234567890');
}Events
Listen for consent lifecycle events:
| Event | Data | Description |
| --------------------- | ----------------------- | ---------------------------------------- |
| consent:given | ConsentRecord | Consent was given or updated. |
| consent:revoked | Previous ConsentRecord| Consent was revoked. |
| category:accepted | string (category key) | A specific category was accepted. |
| category:rejected | string (category key) | A specific category was rejected. |
| banner:shown | — | The banner became visible. |
| banner:hidden | — | The banner was hidden. |
| preferences:shown | — | The preferences modal was opened. |
| preferences:hidden | — | The preferences modal was closed. |
CookieConsent.on('consent:given', function (consent) {
console.log('Consent given:', consent.categories);
});
CookieConsent.on('category:accepted', function (category) {
console.log('Category accepted:', category);
});Preferences Button
Users must be able to change their preferences at any time (GDPR Article 7(3)). There are three ways to set up a preferences button:
1. HTML attribute (recommended for simplicity)
<button data-cc-open-preferences>Cookie Settings</button>
<!-- or a link in your footer -->
<a href="#" data-cc-open-preferences>Manage Cookies</a>Any element with the data-cc-open-preferences attribute is automatically bound.
2. Config selector
CookieConsent.init({
preferencesButton: '#my-cookie-button',
});3. JavaScript API
document.getElementById('my-button').addEventListener('click', function () {
CookieConsent.showPreferences();
});TypeScript Support
Type declarations are included in the package:
import CookieConsent, { CookieConsentConfig, ConsentRecord } from 'cookie-consent-gdpr';
const config: CookieConsentConfig = {
layout: 'bar',
categories: {
necessary: {
enabled: true,
readOnly: true,
title: 'Essential',
description: 'Required for the site to function.',
cookies: [],
},
},
};
CookieConsent.init(config);
CookieConsent.on('consent:given', (consent: ConsentRecord) => {
console.log(consent.id, consent.categories);
});Accessibility
This library follows WCAG 2.1 AA guidelines:
- Focus trapping — when the preferences modal is open, focus is trapped within it
- Keyboard navigation — all interactive elements are reachable via Tab, toggleable via Space/Enter
- ARIA attributes —
role="dialog",aria-modal,aria-label,aria-expandedare used throughout - Focus restoration — when the modal closes, focus returns to the previously focused element
- Reduced motion — all animations are disabled when
prefers-reduced-motion: reduceis set - Screen reader support — proper semantic HTML, labels, and descriptions
- No print output — the banner is hidden in print stylesheets
GDPR Compliance Reference
This library is designed to satisfy the following EU regulations:
GDPR (General Data Protection Regulation)
| Article / Recital | Requirement | How This Library Complies |
| --- | --- | --- |
| Art. 4(11) — Definition of consent | Consent must be freely given, specific, informed, and an unambiguous indication of wishes. | Granular per-category consent. Clear descriptions. No pre-selected checkboxes. Active opt-in required. |
| Art. 6(1)(a) — Lawfulness of processing | Processing based on consent requires valid consent. | No non-essential cookies are set until explicit consent is given. Scripts are blocked via type="text/plain". |
| Art. 7(1) — Demonstrating consent | The controller must be able to demonstrate that the user consented. | Every consent decision generates a record with UUID, timestamp, categories, URL, and user agent. Webhook support for server-side storage. |
| Art. 7(2) — Distinguishable consent | Consent request must be clearly distinguishable, in clear and plain language. | Standalone banner/modal with plain-language descriptions. Not bundled with other terms. |
| Art. 7(3) — Right to withdraw | It must be as easy to withdraw consent as to give it. | Preferences button (always accessible) lets users change or revoke consent at any time. revokeConsent() API available. |
| Art. 7(4) — No bundling | Consent must not be bundled with acceptance of terms or services. | Cookie consent is a standalone interaction, separate from any other site functionality. |
| Art. 13 — Information to be provided | Users must be informed about purposes, recipients, and duration of processing. | Each cookie's name, provider, purpose, expiry, and type are displayed in the preferences modal. |
| Recital 32 — Conditions for consent | Silence, pre-ticked boxes, or inactivity do not constitute consent. | All non-essential categories default to enabled: false. No pre-checked toggles. Users must take affirmative action. |
| Recital 42 — Informed consent | The user must know the identity of the controller and the purposes. | Cookie provider and purpose are disclosed per-cookie in the preferences modal. |
ePrivacy Directive (2002/58/EC, "Cookie Directive")
| Requirement | How This Library Complies |
| --- | --- |
| Prior consent before setting non-essential cookies | Scripts/iframes/pixels are blocked until explicit consent per category. |
| Strictly necessary cookies exempt | The necessary category is always active (readOnly: true) and clearly labeled "Always Active". |
| Clear and comprehensive information | Full cookie tables with name, provider, purpose, duration, and type. |
Key Implementation Details
- No cookies before consent — The library itself only sets one cookie (
cc_consent) which stores the consent decision. No third-party scripts execute until the user opts in. - The consent cookie is functional —
cc_consentis classified as a strictly necessary cookie because it records the user's privacy preferences, which is required for the website to respect their choices. - Consent expiry — Defaults to 365 days. GDPR best practice recommends re-obtaining consent at least every 12 months. Configurable via
cookie.expiryDays. - Config versioning — If you add or change cookies in your configuration, the
configHashchanges and users are automatically re-prompted (satisfying the requirement that consent is specific to the declared purposes).
Disclaimer: While this library implements the technical requirements of GDPR and the ePrivacy Directive, legal compliance also depends on your specific use case, data processing activities, and jurisdiction. Consult a qualified legal professional for advice specific to your situation.
Browser Support
- Chrome 51+
- Firefox 54+
- Safari 10+
- Edge 15+
- Opera 38+
- iOS Safari 10+
- Android Browser 5+
The library uses MutationObserver, requestAnimationFrame, classList, and JSON.parse — all widely supported.
License
MIT — free for personal and commercial use.
