@mihilista/cookie-consent
v3.2.1
Published
GDPR-compliant cookie consent manager for Next.js with Google Consent Mode v2 and Meta Pixel integration.
Maintainers
Readme
Next.js Cookie Consent Manager 🍪
A GDPR-compliant cookie consent manager for Next.js, with Google Consent Mode v2 and Meta Pixel integration built in. Ships React components, a context provider, and a consent-initialization script that wires correctly with Google Tag Manager.
✨ Features
- Google Consent Mode v2 —
analytics_storage,ad_storage,ad_user_data,ad_personalization - Meta Pixel consent handling — automatic
fbq('consent', 'grant'|'revoke')with retry until pixel loads - Pre-GTM consent defaults —
<ConsentInitScript />setsdeniedbefore any tracking loads (GDPR-safe by default) - Region-aware variant —
<ConsentInitScriptWithRegions />for EEA/US/California differentiation - Cookie persistence — 365 days when accepted, 1 day when rejected
- Granular categories — Required (always on), Analytics, Marketing
- Accessible preferences modal — close via the X button, overlay click, or the
Esckey - Customizable copy — every label/body string overridable; works cleanly with
next-intl - Themeable styling — default CSS is a separate import; brand it via
--cc-*CSS variables or replace it entirely
📦 Installation
npm install @mihilista/cookie-consent
# or
pnpm add @mihilista/cookie-consent
# or
yarn add @mihilista/cookie-consentPeer deps: React >= 18.2, React DOM >= 18.2.
🚀 Quick Start (Next.js App Router)
The implementation order is critical for GDPR compliance. ConsentInitScript must run before GTM so default consent is denied before any tag loads.
// app/layout.tsx
import '@mihilista/cookie-consent/styles'; // optional — see Styling below
import {
ConsentInitScript,
CookieConsentProvider,
CookieBanner,
CookiePreferencesModal,
} from '@mihilista/cookie-consent';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
{/* 1. CRITICAL: set default consent to denied BEFORE GTM */}
<ConsentInitScript />
{/* 2. Load Google Tag Manager AFTER ConsentInitScript */}
<script
dangerouslySetInnerHTML={{
__html: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');`,
}}
/>
</head>
<body>
{/* 3. Wrap the app and mount the banner + modal */}
<CookieConsentProvider>
<CookieBanner />
<CookiePreferencesModal />
{children}
</CookieConsentProvider>
</body>
</html>
);
}For region-specific defaults (EEA denied, US granted, California denied), swap <ConsentInitScript /> for <ConsentInitScriptWithRegions />. See IMPLEMENTATION_GUIDE.md.
🧩 API
Components
| Component | Purpose |
|---|---|
| ConsentInitScript | Inline script for <head>. Sets default Google Consent to denied, re-applies any saved consent. Must precede GTM. |
| ConsentInitScriptWithRegions | Region-aware variant. Accepts regions={{ eea, us, california }}. |
| CookieConsentProvider | Context provider. Wraps the app, manages state, handles GTM/Meta Pixel consent updates. |
| CookieBanner | Bottom banner shown when no consent cookie exists. Accepts locale prop. |
| CookiePreferencesModal | Granular preferences modal. Accepts locale prop. Closes on X button, overlay click, or Esc. |
| CookieConsent | Convenience wrapper that mounts Provider + Banner + Modal (use when you don't need custom layout). |
Hook: useCookieConsent()
Returns:
preferences: { analytics: boolean; marketing: boolean }showBanner,setShowBannershowPreferencesModal,setShowPreferencesModalhandleAccept()— accept allhandleReject()— reject allsavePreferences(analytics?, marketing?)— save current or explicit choicestogglePreference('analytics' | 'marketing')hasConsentCookie(),getConsentCookie()CookiePreferencesButton— pre-styled button to reopen the modal
'use client';
import { useCookieConsent } from '@mihilista/cookie-consent';
export function CookieSettingsLink() {
const { CookiePreferencesButton } = useCookieConsent();
return <CookiePreferencesButton>Cookie settings</CookiePreferencesButton>;
}Cookie Storage
A single userConsent cookie is written in Google Consent Mode v2 format:
{
"analytics_storage": "granted" | "denied",
"ad_storage": "granted" | "denied",
"ad_user_data": "granted" | "denied",
"ad_personalization": "granted" | "denied"
}Lifetime: 365 days if any category is granted, 1 day otherwise (so rejecters get re-prompted reasonably soon).
🌐 Localization
Both CookieBanner and CookiePreferencesModal accept a locale prop with overrides for every visible string. Default copy is Czech.
With next-intl
// components/cookie-banner-and-modal.tsx
'use client';
import { CookieBanner, CookiePreferencesModal } from '@mihilista/cookie-consent';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
export default function CookieBannerAndModal() {
const t = useTranslations('cookies');
const bannerLocale = {
title: t('banner.title'),
body: t.rich('banner.body', {
policy: (chunks) => (
<Link href="/legal/privacy-policy" className="underline">
{chunks}
</Link>
),
}),
buttons: {
manage: t('banner.buttons.manage'),
accept: t('banner.buttons.accept'),
reject: t('banner.buttons.reject'),
},
};
return (
<>
<CookieBanner locale={bannerLocale} />
<CookiePreferencesModal locale={t.raw('modal')} />
</>
);
}Plain (no i18n)
<CookieBanner
locale={{
title: '🍪 We use cookies',
body: 'We use cookies to improve your experience.',
link: <a href="/privacy-policy">Privacy Policy</a>,
buttons: { manage: 'Manage', accept: 'Accept', reject: 'Reject' },
}}
/>🎨 Styling
Option 1: Use default styles
import '@mihilista/cookie-consent/styles';Option 1b: Theme via CSS variables (recommended)
Import the default styles, then override the --cc-* custom properties on
:root to match your brand. Every color, radius and shadow is a variable; the
defaults reproduce the original dark look, so you only set what you want to
change.
:root {
--cc-bg: #fffdf8; /* container background */
--cc-fg: #1a1a1a; /* text */
--cc-radius: 0.75rem; /* container corners */
--cc-button-radius: 0.375rem;/* button + checkbox corners */
--cc-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.25);
--cc-shadow-modal: 0 20px 60px -15px rgba(0, 0, 0, 0.35);
--cc-border: rgba(0, 0, 0, 0.12);
--cc-action-bg: #1a1a1a; /* primary button (accept / save) */
--cc-action-fg: #fffdf8;
--cc-action-border: #1a1a1a;
--cc-action-bg-hover: #333;
--cc-action-border-hover: #333;
--cc-overlay: rgba(0, 0, 0, 0.5);
--cc-close: rgba(0, 0, 0, 0.6);
--cc-close-hover: #1a1a1a;
--cc-checkbox-border: rgba(0, 0, 0, 0.12);
--cc-checkbox-checked-bg: #1a1a1a;
--cc-checkbox-checked-fg: #fffdf8;
}Because these are plain CSS variables, you can point them at your own design
tokens (e.g. --cc-bg: var(--color-card)) so the banner tracks your palette
automatically. The full list lives in src/styles/global.css.
Option 2: Bring your own CSS
Skip the styles import. All components carry classes with the cc-- prefix (e.g. cc--banner--container, cc--modal--overlay, cc--buttonBase). Source files in src/styles/ are the authoritative list.
Option 3: Hybrid
import '@mihilista/cookie-consent/styles';
import './cookie-consent-overrides.css';🛡️ GDPR Compliance
This package handles the consent state correctly, but GTM tag configuration is your responsibility. Every analytics/marketing tag in your GTM container must be configured with consent requirements, otherwise tags fire regardless of user choice.
Minimum tag setup:
- GA4: require
analytics_storage - Meta Pixel: require
ad_storage,ad_user_data,ad_personalization. Pixel code must start withfbq('consent', 'revoke'). - Google Ads: require
ad_storage,ad_user_data,ad_personalization
Full walkthrough in IMPLEMENTATION_GUIDE.md.
📖 Further Reading
- IMPLEMENTATION_GUIDE.md — step-by-step GDPR-compliant setup, GTM tag config, testing
- Google Consent Mode v2
- Meta Pixel & GDPR
