cookie-consent-gdpr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,758 @@
1
+ # GDPR Cookie Consent
2
+
3
+ A lightweight (**~9.7 KB gzipped**), fully GDPR & ePrivacy Directive compliant cookie consent banner. Zero dependencies. Framework-agnostic. Production-ready.
4
+
5
+ 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.
6
+
7
+ <p align="center">
8
+ <img src="media/banner.png" alt="Claude Code On The Go Banner">
9
+ </p>
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [Features](#features)
16
+ - [Quick Start](#quick-start)
17
+ - [Installation](#installation)
18
+ - [Configuration](#configuration)
19
+ - [Full Configuration Reference](#full-configuration-reference)
20
+ - [Cookie Categories](#cookie-categories)
21
+ - [Cookie Details](#cookie-details)
22
+ - [Texts / i18n](#texts--i18n)
23
+ - [Theme](#theme)
24
+ - [Webhook (Consent Records)](#webhook-consent-records)
25
+ - [Cross-Domain / Subdomain Cookies](#cross-domain--subdomain-cookies)
26
+ - [Layout Modes](#layout-modes)
27
+ - [HTML Auto-Initialization](#html-auto-initialization)
28
+ - [Script & Element Blocking](#script--element-blocking)
29
+ - [JavaScript API](#javascript-api)
30
+ - [Events](#events)
31
+ - [Preferences Button](#preferences-button)
32
+ - [TypeScript Support](#typescript-support)
33
+ - [Accessibility](#accessibility)
34
+ - [GDPR Compliance Reference](#gdpr-compliance-reference)
35
+ - [Browser Support](#browser-support)
36
+ - [License](#license)
37
+
38
+ ---
39
+
40
+ ## Features
41
+
42
+ - **Three layout modes** — bottom/top bar, centered modal, corner popup
43
+ - **Granular consent** — per-category control (Necessary, Functional, Analytics, Marketing — or define your own)
44
+ - **Full cookie disclosure** — accordion UI showing every cookie's name, provider, purpose, expiry, and type
45
+ - **Automatic script blocking** — scripts, iframes, and images are blocked until the user consents to the relevant category
46
+ - **Consent records** — every consent decision is stored with a UUID, timestamp, categories, URL, and user agent
47
+ - **Webhook support** — optionally POST consent records to your server for GDPR Article 7(1) proof-of-consent
48
+ - **Config versioning** — when you change your cookie configuration, users are automatically re-prompted
49
+ - **Fully customizable** — every text string, color, font, and size is configurable
50
+ - **Accessible** — WCAG 2.1 AA compliant: focus trapping, keyboard navigation, ARIA attributes, reduced-motion support
51
+ - **Zero dependencies** — pure vanilla JavaScript, works with any framework or static site
52
+ - **Tiny footprint** — ~9.7 KB gzipped, ~38 KB minified
53
+ - **Multiple formats** — ESM, CJS, UMD, and standalone IIFE (script tag)
54
+ - **TypeScript declarations** included
55
+ - **Cross-subdomain support** — share consent across subdomains via cookie domain config
56
+ - **No pre-ticked boxes** — GDPR Recital 32 compliant out of the box
57
+ - **Equal reject/accept prominence** — reject button is always visible alongside accept
58
+
59
+ ---
60
+
61
+ ## Quick Start
62
+
63
+ ### Script Tag (Fastest)
64
+
65
+ ```html
66
+ <script src="https://unpkg.com/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>
67
+ <script>
68
+ CookieConsent.init({
69
+ categories: {
70
+ necessary: {
71
+ enabled: true,
72
+ readOnly: true,
73
+ title: 'Strictly Necessary',
74
+ description: 'Essential cookies that make the website work.',
75
+ cookies: [
76
+ {
77
+ name: 'session_id',
78
+ provider: 'yourdomain.com',
79
+ purpose: 'Maintains your session across page requests.',
80
+ expiry: 'Session',
81
+ type: 'HTTP Cookie',
82
+ },
83
+ ],
84
+ },
85
+ analytics: {
86
+ enabled: false,
87
+ readOnly: false,
88
+ title: 'Analytics',
89
+ description: 'Help us understand how visitors interact with our website.',
90
+ cookies: [
91
+ {
92
+ name: '_ga',
93
+ provider: 'Google Analytics',
94
+ purpose: 'Distinguishes unique users by assigning a randomly generated number.',
95
+ expiry: '2 years',
96
+ type: 'HTTP Cookie',
97
+ },
98
+ {
99
+ name: '_ga_*',
100
+ provider: 'Google Analytics',
101
+ purpose: 'Used to persist session state.',
102
+ expiry: '2 years',
103
+ type: 'HTTP Cookie',
104
+ },
105
+ ],
106
+ },
107
+ marketing: {
108
+ enabled: false,
109
+ readOnly: false,
110
+ title: 'Marketing',
111
+ description: 'Used to deliver personalised advertisements.',
112
+ cookies: [
113
+ {
114
+ name: '_fbp',
115
+ provider: 'Facebook',
116
+ purpose: 'Tracks visits across websites for targeted advertising.',
117
+ expiry: '3 months',
118
+ type: 'HTTP Cookie',
119
+ },
120
+ ],
121
+ },
122
+ },
123
+ privacyPolicyUrl: '/privacy-policy',
124
+ });
125
+ </script>
126
+ ```
127
+
128
+ That's it. The banner appears, users make their choice, scripts are blocked/unblocked accordingly, and consent is stored.
129
+
130
+ ---
131
+
132
+ ## Installation
133
+
134
+ ### NPM / Yarn
135
+
136
+ ```bash
137
+ npm install cookie-consent-gdpr
138
+ # or
139
+ yarn add cookie-consent-gdpr
140
+ ```
141
+
142
+ ```js
143
+ import CookieConsent from 'cookie-consent-gdpr';
144
+
145
+ CookieConsent.init({
146
+ // your config
147
+ });
148
+ ```
149
+
150
+ ### CDN (Script Tag)
151
+
152
+ ```html
153
+ <!-- unpkg -->
154
+ <script src="https://unpkg.com/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>
155
+
156
+ <!-- jsDelivr -->
157
+ <script src="https://cdn.jsdelivr.net/npm/cookie-consent-gdpr@latest/dist/cookie-consent-gdpr.min.js"></script>
158
+ ```
159
+
160
+ ### Self-Hosted
161
+
162
+ Download `dist/cookie-consent-gdpr.min.js` from this repository and include it:
163
+
164
+ ```html
165
+ <script src="/path/to/cookie-consent-gdpr.min.js"></script>
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Configuration
171
+
172
+ ### Full Configuration Reference
173
+
174
+ ```js
175
+ CookieConsent.init({
176
+ // ─── Container ────────────────────────────────────────────────
177
+ // CSS selector or DOM element where the banner mounts.
178
+ // If null, a container is auto-created and appended to document.body.
179
+ container: null,
180
+
181
+ // ─── Layout ───────────────────────────────────────────────────
182
+ // 'bar' | 'modal' | 'popup'
183
+ layout: 'bar',
184
+
185
+ // 'bottom' | 'top' (for bar)
186
+ // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' (for popup)
187
+ position: 'bottom',
188
+
189
+ // ─── Behavior ─────────────────────────────────────────────────
190
+ autoShow: true, // Show banner on page load if no consent exists
191
+ reconsentOnChange: true, // Re-prompt when cookie config changes
192
+ forceOverlay: false, // Block page interaction until consent is given
193
+ closeOnBackdrop: false, // Clicking backdrop closes banner (false = strict GDPR)
194
+
195
+ // ─── Preferences Button ───────────────────────────────────────
196
+ // Selector or element. Also auto-binds [data-cc-open-preferences].
197
+ preferencesButton: null,
198
+
199
+ // ─── Privacy Policy ───────────────────────────────────────────
200
+ privacyPolicyUrl: '',
201
+
202
+ // ─── Cookie Storage ───────────────────────────────────────────
203
+ cookie: {
204
+ name: 'cc_consent', // Consent cookie name
205
+ domain: '', // '' = current domain. '.example.com' = all subdomains
206
+ path: '/',
207
+ expiryDays: 365, // GDPR recommends ≤12 months
208
+ sameSite: 'Lax', // 'Lax' | 'Strict' | 'None'
209
+ secure: true, // Auto-detected from protocol by default
210
+ },
211
+
212
+ // ─── Webhook ──────────────────────────────────────────────────
213
+ webhook: {
214
+ url: null, // POST endpoint for consent records. null = disabled.
215
+ headers: {}, // Additional headers (e.g. authorization)
216
+ },
217
+
218
+ // ─── Categories ───────────────────────────────────────────────
219
+ categories: {
220
+ necessary: {
221
+ enabled: true,
222
+ readOnly: true,
223
+ title: 'Strictly Necessary',
224
+ description: '...',
225
+ cookies: [],
226
+ },
227
+ functional: {
228
+ enabled: false,
229
+ readOnly: false,
230
+ title: 'Functional',
231
+ description: '...',
232
+ cookies: [],
233
+ },
234
+ analytics: {
235
+ enabled: false,
236
+ readOnly: false,
237
+ title: 'Analytics',
238
+ description: '...',
239
+ cookies: [],
240
+ },
241
+ marketing: {
242
+ enabled: false,
243
+ readOnly: false,
244
+ title: 'Marketing',
245
+ description: '...',
246
+ cookies: [],
247
+ },
248
+ },
249
+
250
+ // ─── Texts (i18n) ────────────────────────────────────────────
251
+ texts: { /* see Texts section below */ },
252
+
253
+ // ─── Theme ────────────────────────────────────────────────────
254
+ theme: { /* see Theme section below */ },
255
+
256
+ // ─── Callbacks ────────────────────────────────────────────────
257
+ onConsent: function (consent) {},
258
+ onRevoke: function (consent) {},
259
+ onAccept: function (category) {},
260
+ onReject: function (category) {},
261
+ });
262
+ ```
263
+
264
+ ### Cookie Categories
265
+
266
+ Define as many categories as you need. Each category requires:
267
+
268
+ | Property | Type | Description |
269
+ | ------------- | ---------- | -------------------------------------------------------- |
270
+ | `enabled` | `boolean` | Default state. **Must be `false`** for non-necessary categories (GDPR Recital 32). |
271
+ | `readOnly` | `boolean` | If `true`, the user cannot toggle this off. Use for strictly necessary cookies. |
272
+ | `title` | `string` | Display title shown in the preferences modal. |
273
+ | `description` | `string` | Explains what this category is for. |
274
+ | `cookies` | `Array` | List of cookie detail objects (see below). |
275
+
276
+ Standard categories: `necessary`, `functional`, `analytics`, `marketing`. You can add custom ones:
277
+
278
+ ```js
279
+ categories: {
280
+ necessary: { enabled: true, readOnly: true, title: 'Essential', ... },
281
+ social_media: {
282
+ enabled: false,
283
+ readOnly: false,
284
+ title: 'Social Media',
285
+ description: 'Cookies used by social media embeds and sharing widgets.',
286
+ cookies: [
287
+ { name: '__stripe_mid', provider: 'Stripe', purpose: 'Fraud prevention', expiry: '1 year', type: 'HTTP Cookie' }
288
+ ],
289
+ },
290
+ },
291
+ ```
292
+
293
+ ### Cookie Details
294
+
295
+ Each entry in a category's `cookies` array should provide the information required by GDPR Article 13:
296
+
297
+ | Property | Type | Description |
298
+ | ---------- | -------- | -------------------------------------------------- |
299
+ | `name` | `string` | Cookie name or wildcard pattern (e.g. `_ga_*`) |
300
+ | `provider` | `string` | Who sets this cookie (domain or company name) |
301
+ | `purpose` | `string` | Human-readable explanation of what it does |
302
+ | `expiry` | `string` | Duration (e.g. `1 year`, `Session`, `30 days`) |
303
+ | `type` | `string` | `HTTP Cookie`, `localStorage`, `sessionStorage`, `Pixel` |
304
+
305
+ All cookie details are displayed in the preferences modal accordion — this is required by the ePrivacy Directive.
306
+
307
+ ### Texts / i18n
308
+
309
+ Override any text string to support your language:
310
+
311
+ ```js
312
+ texts: {
313
+ bannerTitle: 'We use cookies',
314
+ bannerDescription: 'We use cookies to improve your experience...',
315
+ acceptAll: 'Accept All',
316
+ rejectAll: 'Reject All',
317
+ settings: 'Cookie Settings',
318
+ preferencesTitle: 'Cookie Preferences',
319
+ preferencesDescription: 'Choose which cookies you want to allow...',
320
+ save: 'Save Preferences',
321
+ acceptAllPreferences: 'Accept All',
322
+ rejectAllPreferences: 'Reject All',
323
+ alwaysActive: 'Always Active',
324
+ cookieNameLabel: 'Name',
325
+ cookieProviderLabel: 'Provider',
326
+ cookiePurposeLabel: 'Purpose',
327
+ cookieExpiryLabel: 'Expiry',
328
+ cookieTypeLabel: 'Type',
329
+ noCookies: 'No cookies to display.',
330
+ privacyPolicyLabel: 'Privacy Policy',
331
+ poweredBy: '', // Optional branding text
332
+ }
333
+ ```
334
+
335
+ **Example: German**
336
+
337
+ ```js
338
+ texts: {
339
+ bannerTitle: 'Wir verwenden Cookies',
340
+ bannerDescription: 'Wir verwenden Cookies und ähnliche Technologien...',
341
+ acceptAll: 'Alle akzeptieren',
342
+ rejectAll: 'Alle ablehnen',
343
+ settings: 'Cookie-Einstellungen',
344
+ preferencesTitle: 'Cookie-Einstellungen',
345
+ preferencesDescription: 'Hier können Sie Ihre Cookie-Präferenzen verwalten...',
346
+ save: 'Einstellungen speichern',
347
+ acceptAllPreferences: 'Alle akzeptieren',
348
+ rejectAllPreferences: 'Alle ablehnen',
349
+ alwaysActive: 'Immer aktiv',
350
+ }
351
+ ```
352
+
353
+ ### Theme
354
+
355
+ Customize every visual aspect:
356
+
357
+ ```js
358
+ theme: {
359
+ primary: '#0e6b4e', // Primary button & accent color
360
+ primaryHover: '#0a5a40', // Primary button hover state
361
+ primaryText: '#ffffff', // Text on primary buttons
362
+ background: '#ffffff', // Banner/modal background
363
+ text: '#333333', // Primary text color
364
+ textSecondary: '#666666', // Secondary/description text
365
+ border: '#e0e0e0', // Borders and dividers
366
+ overlay: 'rgba(0,0,0,0.55)', // Backdrop overlay color
367
+ toggleOn: '#0e6b4e', // Toggle switch ON color
368
+ toggleOff: '#cccccc', // Toggle switch OFF color
369
+ toggleKnob: '#ffffff', // Toggle knob color
370
+ fontFamily: "'Inter', sans-serif",
371
+ fontSize: '14px',
372
+ borderRadius: '8px',
373
+ zIndex: 2147483645, // Ensures banner is above everything
374
+ maxWidth: '1140px', // Max width for bar layout
375
+ popupWidth: '400px', // Width for popup layout
376
+ }
377
+ ```
378
+
379
+ You can also override theme values directly in CSS using custom properties:
380
+
381
+ ```css
382
+ .cc-container {
383
+ --cc-primary: #ff6600;
384
+ --cc-bg: #1a1a2e;
385
+ --cc-text: #eaeaea;
386
+ }
387
+ ```
388
+
389
+ ### Webhook (Consent Records)
390
+
391
+ 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.
392
+
393
+ ```js
394
+ webhook: {
395
+ url: 'https://api.yoursite.com/consent',
396
+ headers: {
397
+ 'Authorization': 'Bearer your-api-key',
398
+ },
399
+ }
400
+ ```
401
+
402
+ When consent is given or updated, a `POST` request is sent with this payload:
403
+
404
+ ```json
405
+ {
406
+ "consentId": "a1b2c3d4-e5f6-...",
407
+ "timestamp": "2026-02-23T14:30:00.000Z",
408
+ "categories": {
409
+ "necessary": true,
410
+ "functional": false,
411
+ "analytics": true,
412
+ "marketing": false
413
+ },
414
+ "url": "https://yoursite.com/page",
415
+ "userAgent": "Mozilla/5.0 ...",
416
+ "configHash": "k8m3x"
417
+ }
418
+ ```
419
+
420
+ ### Cross-Domain / Subdomain Cookies
421
+
422
+ To share consent across subdomains (e.g. `www.example.com` and `app.example.com`):
423
+
424
+ ```js
425
+ cookie: {
426
+ domain: '.example.com', // Note the leading dot
427
+ }
428
+ ```
429
+
430
+ This sets the consent cookie on the parent domain, making it accessible to all subdomains.
431
+
432
+ For true cross-origin consent (different TLDs), you would need a server-side approach using the webhook to synchronize consent records.
433
+
434
+ ---
435
+
436
+ ## Layout Modes
437
+
438
+ ### Bottom Bar (default)
439
+
440
+ ```js
441
+ CookieConsent.init({
442
+ layout: 'bar',
443
+ position: 'bottom', // or 'top'
444
+ });
445
+ ```
446
+
447
+ A full-width bar fixed to the bottom (or top) of the viewport. Most common pattern.
448
+
449
+ ### Center Modal
450
+
451
+ ```js
452
+ CookieConsent.init({
453
+ layout: 'modal',
454
+ forceOverlay: true, // recommended for modal
455
+ });
456
+ ```
457
+
458
+ A centered dialog with backdrop. Good for high-consent-rate scenarios.
459
+
460
+ ### Corner Popup
461
+
462
+ ```js
463
+ CookieConsent.init({
464
+ layout: 'popup',
465
+ position: 'bottom-right', // or 'bottom-left', 'top-right', 'top-left'
466
+ });
467
+ ```
468
+
469
+ A small card positioned in the corner. Less intrusive.
470
+
471
+ ---
472
+
473
+ ## HTML Auto-Initialization
474
+
475
+ For users with minimal JavaScript knowledge, the banner can auto-initialize using HTML attributes:
476
+
477
+ ### Option 1: Script attribute
478
+
479
+ ```html
480
+ <script
481
+ src="cookie-consent-gdpr.min.js"
482
+ data-cc-auto
483
+ data-cc-config='{"privacyPolicyUrl": "/privacy", "categories": { ... }}'
484
+ ></script>
485
+ ```
486
+
487
+ ### Option 2: Container element
488
+
489
+ ```html
490
+ <div id="cc-banner" data-cc-auto data-cc-config='{"layout": "popup"}'></div>
491
+ <script src="cookie-consent-gdpr.min.js"></script>
492
+ ```
493
+
494
+ The `data-cc-config` attribute accepts a JSON string with any configuration options. If omitted, default settings are used.
495
+
496
+ ---
497
+
498
+ ## Script & Element Blocking
499
+
500
+ The most important GDPR requirement: **no non-essential cookies may be set before consent is given.** This library provides automatic blocking.
501
+
502
+ ### Blocking Scripts
503
+
504
+ Change `type="text/javascript"` to `type="text/plain"` and add a `data-cookiecategory` attribute:
505
+
506
+ ```html
507
+ <!-- This script will NOT execute until the user consents to "analytics" -->
508
+ <script
509
+ type="text/plain"
510
+ data-cookiecategory="analytics"
511
+ src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"
512
+ ></script>
513
+
514
+ <!-- Inline scripts work too -->
515
+ <script type="text/plain" data-cookiecategory="analytics">
516
+ window.dataLayer = window.dataLayer || [];
517
+ function gtag(){ dataLayer.push(arguments); }
518
+ gtag('js', new Date());
519
+ gtag('config', 'G-XXXXXX');
520
+ </script>
521
+ ```
522
+
523
+ ### Blocking Iframes
524
+
525
+ Use `data-src` instead of `src`:
526
+
527
+ ```html
528
+ <!-- YouTube embed blocked until marketing consent -->
529
+ <iframe
530
+ data-cookiecategory="marketing"
531
+ data-src="https://www.youtube.com/embed/dQw4w9WgXcQ"
532
+ src="about:blank"
533
+ width="560"
534
+ height="315"
535
+ ></iframe>
536
+ ```
537
+
538
+ ### Blocking Images (Tracking Pixels)
539
+
540
+ ```html
541
+ <img
542
+ data-cookiecategory="analytics"
543
+ data-src="https://analytics.example.com/pixel.gif"
544
+ src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
545
+ />
546
+ ```
547
+
548
+ 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`.
549
+
550
+ A **MutationObserver** also watches for dynamically added elements, so scripts injected after page load are handled too.
551
+
552
+ ---
553
+
554
+ ## JavaScript API
555
+
556
+ All methods return the `CookieConsent` object for chaining (except getters).
557
+
558
+ | Method | Description |
559
+ | ------------------------------- | -------------------------------------------------- |
560
+ | `CookieConsent.init(config)` | Initialize with configuration. |
561
+ | `CookieConsent.show()` | Show the consent banner. |
562
+ | `CookieConsent.hide()` | Hide the consent banner. |
563
+ | `CookieConsent.showPreferences()` | Open the preferences/settings modal. |
564
+ | `CookieConsent.hidePreferences()` | Close the preferences modal. |
565
+ | `CookieConsent.acceptAll()` | Accept all cookie categories. |
566
+ | `CookieConsent.rejectAll()` | Reject all non-essential categories. |
567
+ | `CookieConsent.acceptCategory(key)` | Accept a specific category by key. |
568
+ | `CookieConsent.getConsent()` | Get the full consent record (or `null`). |
569
+ | `CookieConsent.hasConsented()` | `true` if the user has made any consent decision. |
570
+ | `CookieConsent.hasCategory(key)` | `true` if a specific category is consented. |
571
+ | `CookieConsent.revokeConsent()` | Revoke consent, delete cookie, re-show banner. |
572
+ | `CookieConsent.on(event, fn)` | Register an event listener. |
573
+ | `CookieConsent.off(event, fn)` | Remove an event listener. |
574
+ | `CookieConsent.destroy()` | Fully remove UI and clean up all listeners. |
575
+ | `CookieConsent.getConfig()` | Get a read-only copy of the active configuration. |
576
+
577
+ ### Example: Conditional Loading
578
+
579
+ ```js
580
+ CookieConsent.init({
581
+ onConsent: function (consent) {
582
+ if (consent.categories.analytics) {
583
+ // Load Google Analytics dynamically
584
+ var script = document.createElement('script');
585
+ script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXX';
586
+ document.head.appendChild(script);
587
+ }
588
+ },
589
+ });
590
+ ```
591
+
592
+ ### Example: Check Consent Before Action
593
+
594
+ ```js
595
+ if (CookieConsent.hasCategory('marketing')) {
596
+ // Safe to initialize Facebook Pixel
597
+ fbq('init', '1234567890');
598
+ }
599
+ ```
600
+
601
+ ---
602
+
603
+ ## Events
604
+
605
+ Listen for consent lifecycle events:
606
+
607
+ | Event | Data | Description |
608
+ | --------------------- | ----------------------- | ---------------------------------------- |
609
+ | `consent:given` | `ConsentRecord` | Consent was given or updated. |
610
+ | `consent:revoked` | Previous `ConsentRecord`| Consent was revoked. |
611
+ | `category:accepted` | `string` (category key) | A specific category was accepted. |
612
+ | `category:rejected` | `string` (category key) | A specific category was rejected. |
613
+ | `banner:shown` | — | The banner became visible. |
614
+ | `banner:hidden` | — | The banner was hidden. |
615
+ | `preferences:shown` | — | The preferences modal was opened. |
616
+ | `preferences:hidden` | — | The preferences modal was closed. |
617
+
618
+ ```js
619
+ CookieConsent.on('consent:given', function (consent) {
620
+ console.log('Consent given:', consent.categories);
621
+ });
622
+
623
+ CookieConsent.on('category:accepted', function (category) {
624
+ console.log('Category accepted:', category);
625
+ });
626
+ ```
627
+
628
+ ---
629
+
630
+ ## Preferences Button
631
+
632
+ 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:
633
+
634
+ ### 1. HTML attribute (recommended for simplicity)
635
+
636
+ ```html
637
+ <button data-cc-open-preferences>Cookie Settings</button>
638
+ <!-- or a link in your footer -->
639
+ <a href="#" data-cc-open-preferences>Manage Cookies</a>
640
+ ```
641
+
642
+ Any element with the `data-cc-open-preferences` attribute is automatically bound.
643
+
644
+ ### 2. Config selector
645
+
646
+ ```js
647
+ CookieConsent.init({
648
+ preferencesButton: '#my-cookie-button',
649
+ });
650
+ ```
651
+
652
+ ### 3. JavaScript API
653
+
654
+ ```js
655
+ document.getElementById('my-button').addEventListener('click', function () {
656
+ CookieConsent.showPreferences();
657
+ });
658
+ ```
659
+
660
+ ---
661
+
662
+ ## TypeScript Support
663
+
664
+ Type declarations are included in the package:
665
+
666
+ ```ts
667
+ import CookieConsent, { CookieConsentConfig, ConsentRecord } from 'cookie-consent-gdpr';
668
+
669
+ const config: CookieConsentConfig = {
670
+ layout: 'bar',
671
+ categories: {
672
+ necessary: {
673
+ enabled: true,
674
+ readOnly: true,
675
+ title: 'Essential',
676
+ description: 'Required for the site to function.',
677
+ cookies: [],
678
+ },
679
+ },
680
+ };
681
+
682
+ CookieConsent.init(config);
683
+
684
+ CookieConsent.on('consent:given', (consent: ConsentRecord) => {
685
+ console.log(consent.id, consent.categories);
686
+ });
687
+ ```
688
+
689
+ ---
690
+
691
+ ## Accessibility
692
+
693
+ This library follows WCAG 2.1 AA guidelines:
694
+
695
+ - **Focus trapping** — when the preferences modal is open, focus is trapped within it
696
+ - **Keyboard navigation** — all interactive elements are reachable via Tab, toggleable via Space/Enter
697
+ - **ARIA attributes** — `role="dialog"`, `aria-modal`, `aria-label`, `aria-expanded` are used throughout
698
+ - **Focus restoration** — when the modal closes, focus returns to the previously focused element
699
+ - **Reduced motion** — all animations are disabled when `prefers-reduced-motion: reduce` is set
700
+ - **Screen reader support** — proper semantic HTML, labels, and descriptions
701
+ - **No print output** — the banner is hidden in print stylesheets
702
+
703
+ ---
704
+
705
+ ## GDPR Compliance Reference
706
+
707
+ This library is designed to satisfy the following EU regulations:
708
+
709
+ ### GDPR (General Data Protection Regulation)
710
+
711
+ | Article / Recital | Requirement | How This Library Complies |
712
+ | --- | --- | --- |
713
+ | **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. |
714
+ | **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"`. |
715
+ | **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. |
716
+ | **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. |
717
+ | **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. |
718
+ | **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. |
719
+ | **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. |
720
+ | **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. |
721
+ | **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. |
722
+
723
+ ### ePrivacy Directive (2002/58/EC, "Cookie Directive")
724
+
725
+ | Requirement | How This Library Complies |
726
+ | --- | --- |
727
+ | Prior consent before setting non-essential cookies | Scripts/iframes/pixels are blocked until explicit consent per category. |
728
+ | Strictly necessary cookies exempt | The `necessary` category is always active (`readOnly: true`) and clearly labeled "Always Active". |
729
+ | Clear and comprehensive information | Full cookie tables with name, provider, purpose, duration, and type. |
730
+
731
+ ### Key Implementation Details
732
+
733
+ 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.
734
+ 2. **The consent cookie is functional** — `cc_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.
735
+ 3. **Consent expiry** — Defaults to 365 days. GDPR best practice recommends re-obtaining consent at least every 12 months. Configurable via `cookie.expiryDays`.
736
+ 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).
737
+
738
+ > **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.
739
+
740
+ ---
741
+
742
+ ## Browser Support
743
+
744
+ - Chrome 51+
745
+ - Firefox 54+
746
+ - Safari 10+
747
+ - Edge 15+
748
+ - Opera 38+
749
+ - iOS Safari 10+
750
+ - Android Browser 5+
751
+
752
+ The library uses `MutationObserver`, `requestAnimationFrame`, `classList`, and `JSON.parse` — all widely supported.
753
+
754
+ ---
755
+
756
+ ## License
757
+
758
+ [MIT](LICENSE) — free for personal and commercial use.