npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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

  • 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-gdpr
import 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 attributesrole="dialog", aria-modal, aria-label, aria-expanded are 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: reduce is 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

  1. 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.
  2. The consent cookie is functionalcc_consent is classified as a strictly necessary cookie because it records the user's privacy preferences, which is required for the website to respect their choices.
  3. Consent expiry — Defaults to 365 days. GDPR best practice recommends re-obtaining consent at least every 12 months. Configurable via cookie.expiryDays.
  4. Config versioning — If you add or change cookies in your configuration, the configHash changes 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.