Claude
Skills
Sign in
Back

cookie-consent

Included with Lifetime
$97 forever

Implement GDPR/ePrivacy-compliant cookie consent management. Use when adding a cookie banner, managing marketing consent, achieving GDPR compliance for EU users, or integrating consent management with analytics platforms.

Data & Analytics

What this skill does


# Cookie Consent

## Overview

The EU ePrivacy Directive (Cookie Law) and GDPR require informed, freely given, granular, and withdrawable consent before placing non-essential cookies. The UK PECR applies post-Brexit. Fines under GDPR can reach 4% of global annual turnover.

**Strictly necessary cookies** (no consent required): session tokens, shopping cart, security tokens, load balancing.

**All others require consent**: analytics, advertising, personalization, functional enhancements.

## Cookie Categories

| Category | Examples | Consent Required |
|----------|----------|-----------------|
| **Strictly Necessary** | Auth session, CSRF token, cart | ❌ No |
| **Functional** | Language preference, UI theme | ✅ Yes (GDPR) |
| **Analytics** | Google Analytics, Mixpanel, Hotjar | ✅ Yes |
| **Marketing** | Facebook Pixel, Google Ads, ad targeting | ✅ Yes |

## Consent Requirements (GDPR Article 7)

1. **Granular**: Per-category consent (not all-or-nothing)
2. **Freely given**: "Accept all" ≠ better treatment; reject must be as easy as accept
3. **Informed**: Clear explanation of what each category does
4. **Withdrawable**: Users can change or revoke consent at any time
5. **Documented**: Store a consent record (who consented, when, to what, via which version)

## Custom Consent Banner (React/Next.js)

```tsx
// components/CookieConsent.tsx
import { useState, useEffect } from 'react';

interface ConsentPreferences {
  strictly_necessary: true;  // Always true — cannot be disabled
  functional: boolean;
  analytics: boolean;
  marketing: boolean;
}

interface ConsentRecord {
  version: string;
  timestamp: string;
  preferences: ConsentPreferences;
  source: 'banner' | 'settings' | 'gpc';
}

const CONSENT_VERSION = '2024-01-01';
const CONSENT_COOKIE_NAME = 'consent_preferences';

export function CookieConsent() {
  const [showBanner, setShowBanner] = useState(false);
  const [showDetails, setShowDetails] = useState(false);
  const [preferences, setPreferences] = useState<ConsentPreferences>({
    strictly_necessary: true,
    functional: false,
    analytics: false,
    marketing: false,
  });

  useEffect(() => {
    // Check for GPC signal first
    if (navigator.globalPrivacyControl) {
      const gpcConsent: ConsentRecord = {
        version: CONSENT_VERSION,
        timestamp: new Date().toISOString(),
        preferences: { strictly_necessary: true, functional: false, analytics: false, marketing: false },
        source: 'gpc',
      };
      saveConsent(gpcConsent);
      return;
    }

    // Check if consent already given
    const saved = getStoredConsent();
    if (!saved || saved.version !== CONSENT_VERSION) {
      setShowBanner(true);
    }
  }, []);

  const acceptAll = () => {
    const allConsent: ConsentPreferences = {
      strictly_necessary: true,
      functional: true,
      analytics: true,
      marketing: true,
    };
    const record: ConsentRecord = {
      version: CONSENT_VERSION,
      timestamp: new Date().toISOString(),
      preferences: allConsent,
      source: 'banner',
    };
    saveConsent(record);
    applyConsent(allConsent);
    setShowBanner(false);
  };

  const rejectAll = () => {
    const minimalConsent: ConsentPreferences = {
      strictly_necessary: true,
      functional: false,
      analytics: false,
      marketing: false,
    };
    const record: ConsentRecord = {
      version: CONSENT_VERSION,
      timestamp: new Date().toISOString(),
      preferences: minimalConsent,
      source: 'banner',
    };
    saveConsent(record);
    applyConsent(minimalConsent);
    setShowBanner(false);
  };

  const saveCustom = () => {
    const record: ConsentRecord = {
      version: CONSENT_VERSION,
      timestamp: new Date().toISOString(),
      preferences: preferences,
      source: 'banner',
    };
    saveConsent(record);
    applyConsent(preferences);
    setShowBanner(false);
  };

  if (!showBanner) return null;

  return (
    <div className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t shadow-lg p-6">
      <div className="max-w-4xl mx-auto">
        <h2 className="text-lg font-semibold mb-2">Cookie Preferences</h2>
        <p className="text-sm text-gray-600 mb-4">
          We use cookies to improve your experience. You can choose which categories to allow.
          <a href="/privacy-policy" className="underline ml-1">Learn more</a>
        </p>

        {showDetails && (
          <div className="mb-4 space-y-3">
            {[
              { key: 'strictly_necessary', label: 'Strictly Necessary', desc: 'Required for the site to function. Cannot be disabled.', locked: true },
              { key: 'functional', label: 'Functional', desc: 'Remember your preferences (language, theme).' },
              { key: 'analytics', label: 'Analytics', desc: 'Help us understand how you use our site (Google Analytics, Mixpanel).' },
              { key: 'marketing', label: 'Marketing', desc: 'Show relevant ads and measure campaign effectiveness.' },
            ].map(({ key, label, desc, locked }) => (
              <div key={key} className="flex items-start gap-3">
                <input
                  type="checkbox"
                  id={key}
                  checked={preferences[key as keyof ConsentPreferences] as boolean}
                  disabled={locked}
                  onChange={(e) => setPreferences(prev => ({ ...prev, [key]: e.target.checked }))}
                  className="mt-1"
                />
                <label htmlFor={key} className="text-sm">
                  <strong>{label}</strong> {locked && <span className="text-gray-400">(Always on)</span>}
                  <p className="text-gray-500">{desc}</p>
                </label>
              </div>
            ))}
          </div>
        )}

        <div className="flex flex-wrap gap-2">
          <button onClick={rejectAll} className="px-4 py-2 border rounded text-sm">Reject All</button>
          <button onClick={() => setShowDetails(!showDetails)} className="px-4 py-2 border rounded text-sm">
            {showDetails ? 'Hide Details' : 'Customize'}
          </button>
          {showDetails && (
            <button onClick={saveCustom} className="px-4 py-2 bg-blue-600 text-white rounded text-sm">Save Preferences</button>
          )}
          <button onClick={acceptAll} className="px-4 py-2 bg-blue-600 text-white rounded text-sm">Accept All</button>
        </div>
      </div>
    </div>
  );
}
```

## Consent Storage and Retrieval

```typescript
// lib/consent.ts

const CONSENT_KEY = 'consent_v2';

export function saveConsent(record: ConsentRecord): void {
  // Store in localStorage for JS access
  localStorage.setItem(CONSENT_KEY, JSON.stringify(record));
  
  // Also set a cookie for server-side access
  const expires = new Date();
  expires.setFullYear(expires.getFullYear() + 1);
  document.cookie = `${CONSENT_KEY}=${encodeURIComponent(JSON.stringify(record))}; expires=${expires.toUTCString()}; path=/; SameSite=Strict; Secure`;
  
  // Optionally send to your backend for audit trail
  fetch('/api/consent', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(record),
    keepalive: true,
  }).catch(() => {}); // Best effort
}

export function getStoredConsent(): ConsentRecord | null {
  try {
    const stored = localStorage.getItem(CONSENT_KEY);
    return stored ? JSON.parse(stored) : null;
  } catch {
    return null;
  }
}

export function hasConsent(category: keyof ConsentPreferences): boolean {
  const consent = getStoredConsent();
  return consent?.preferences[category] === true;
}
```

## Consent-Gated Analytics

```typescript
// lib/analytics.ts — Only load analytics after consent

export function applyConsent(preferences: ConsentPreferences): void {
  if (preferences.analytics) {
    loadGoogleAnalytics();
    loadMixpanel();
  } else {
    // Opt out / remove tracking
    window['ga-disable-G-XXXXXXXX'] = true;
    // Remove existing analytics cookies
    d

Related in Data & Analytics