GDPR-compliant cookie consent with Google Consent Mode v2 support for Vue 3, Quasar, VitePress, and plain HTML.
Documentation · GitHub · npm
- Google Consent Mode v2 — Full support for
analytics_storage,ad_storage,ad_user_data,ad_personalization - GDPR & CCPA — Compliant with EU GDPR and California Consumer Privacy Act
- GDPR Roaming Protection — Re-prompt consent when EU user travels to non-EU region
- EU Detection — Auto-detect EU users via Cloudflare headers, IP API, or timezone heuristics
- Consent Banner — Customizable GDPR/CCPA banner with dark mode support
- Preference Center — OneTrust-style modal with category toggles (necessary, analytics, marketing, functional)
- Script Blocking — Block third-party scripts until consent is granted
- i18n — 13 built-in locales (en, de, fr, es, it, pt, nl, pl, ru, uk, ja, zh, ko)
- Remote Storage — Pluggable backend for cross-device consent sync with retry support
- GA4 Event Tracking — Typed helpers for ecommerce and conversion events
- Framework Support — Vue 3, Quasar, VitePress, Nuxt 3
- Vanilla JS — Framework-agnostic entry point (
/vanilla) for non-Vue projects - UMD/CDN — Use via
<script>tag, no build tools needed - TypeScript — Full type safety
- Lightweight — ~11kB gzip (UMD), no external dependencies
- SSR Safe — Works with server-side rendering
- Accessible — ARIA-compliant components with focus trap
npm install @structured-world/vue-privacy
# or
yarn add @structured-world/vue-privacy
# or
pnpm add @structured-world/vue-privacyimport { createApp } from 'vue';
import { createConsentPlugin } from '@structured-world/vue-privacy/vue';
import router from './router';
import App from './App.vue';
const app = createApp(App);
app.use(router);
app.use(createConsentPlugin({
gaId: 'G-XXXXXXXXXX',
euDetection: 'auto',
router: router, // Enables automatic SPA page tracking
}));
app.mount('#app');<template>
<div id="app">
<ConsentBanner position="bottom" />
<ConsentPreferenceModal />
</div>
</template>TypeScript users: Add
vue-routeras a dev dependency for type resolution:npm i -D vue-router(not needed at runtime)
// docs/.vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import { enhanceWithConsent } from '@structured-world/vue-privacy/vitepress';
export default enhanceWithConsent(DefaultTheme, {
gaId: 'G-XXXXXXXXXX',
});Fire GA4 events from frontmatter:
---
ga4Title: Pricing Page
ga4Event:
name: view_pricing
params:
page_type: pricing
---// src/boot/consent.ts
import { boot } from 'quasar/wrappers';
import { consentBoot } from '@structured-world/vue-privacy/quasar';
export default boot(consentBoot({
gaId: 'G-XXXXXXXXXX',
}));<script src="https://unpkg.com/@structured-world/vue-privacy"></script>
<script>
const manager = VuePrivacy.createConsentManager({
gaId: 'G-XXXXXXXXXX',
euDetection: 'auto',
});
manager.init();
</script>For projects without Vue, use the /vanilla entry point with pre-built CSS:
import { createConsentManager } from '@structured-world/vue-privacy';
import { createBanner, createModal } from '@structured-world/vue-privacy/vanilla';
import '@structured-world/vue-privacy/banner.css';
import '@structured-world/vue-privacy/modal.css';
const manager = createConsentManager({ gaId: 'G-XXXXXXXXXX' });
await manager.init();
createBanner({ manager, position: 'bottom' });
createModal({ manager });interface ConsentConfig {
// Google Analytics measurement ID
gaId?: string;
// Locale for UI text (auto-detected if not set)
// Supported: en, de, fr, es, it, pt, nl, pl, ru, uk, ja, zh, ko
locale?: SupportedLocale;
// Consent categories
categories?: {
analytics?: boolean; // Default: false
marketing?: boolean; // Default: false
functional?: boolean; // Default: true
};
// Banner UI
banner?: {
title?: string;
message?: string;
acceptAll?: string;
rejectAll?: string;
customize?: string;
privacyLink?: string;
privacyLinkText?: string;
};
// Preference center UI
preferenceCenter?: {
title?: string;
description?: string;
savePreferences?: string;
acceptAll?: string;
categories?: {
necessary?: { name?: string; description?: string };
analytics?: { name?: string; description?: string };
marketing?: { name?: string; description?: string };
functional?: { name?: string; description?: string };
};
};
// Cookie settings
cookie?: {
name?: string; // Default: 'consent_preferences'
expiry?: number; // Days, default: 365
domain?: string;
path?: string; // Default: '/'
};
// Remote consent storage (pluggable backend)
storage?: ConsentStorage;
// EU detection mode
euDetection?: 'auto' | 'cloudflare' | 'api' | 'always' | 'never';
// Consent version (changing resets all consents)
version?: string;
// Callbacks
onConsentChange?: (consent: StoredConsent) => void;
onBannerShow?: () => void;
onBannerHide?: () => void;
onPreferenceCenterShow?: () => void;
onPreferenceCenterHide?: () => void;
}<script setup>
import { useConsent } from '@structured-world/vue-privacy/vue';
const {
// Consent management
acceptAll,
rejectAll,
hasConsent,
resetConsent,
showPreferenceCenter,
// GA4 event tracking
trackEvent,
trackPurchase,
trackAddToCart,
trackViewItem,
trackSignUp,
trackLogin,
} = useConsent();
// Track custom event
trackEvent('button_click', { button_id: 'hero-cta' });
// Track purchase
trackPurchase({
transaction_id: 'T12345',
value: 99.99,
currency: 'USD',
items: [{ item_id: 'SKU123', item_name: 'Product', price: 99.99 }],
});
</script>
<template>
<button @click="showPreferenceCenter">Manage Cookies</button>
</template>Block third-party scripts until consent is granted:
<script type="text/plain" data-consent-category="analytics"
src="https://example.com/analytics.js"></script>
<script type="text/plain" data-consent-category="marketing"
src="https://example.com/ads.js"></script>Scripts are automatically unblocked when the matching category is accepted.
Sync consent across devices with a pluggable backend:
import { createConsentManager, createKVStorage } from '@structured-world/vue-privacy';
// Built-in Cloudflare KV adapter with retry on rate limit
const manager = createConsentManager({
gaId: 'G-XXXXXXXXXX',
storage: createKVStorage('/api/consent', {
maxRetries: 3, // Retry up to 3 times on 429 responses
onRateLimited: (retryAfter, attempt) => {
console.log(`Rate limited, attempt ${attempt}`);
},
}),
});
// Or implement your own
const manager = createConsentManager({
storage: {
get: (uid, version) => fetch(`/api/consent?id=${uid}`).then(r => r.json()),
set: (uid, consent) => fetch('/api/consent', {
method: 'POST',
body: JSON.stringify({ id: uid, ...consent }),
}).then(r => r.json()).then(d => d.id),
},
});import { createConsentManager, VERSION } from '@structured-world/vue-privacy';
console.log('Vue Privacy version:', VERSION); // e.g., "1.10.0"
const manager = createConsentManager({
gaId: 'G-XXXXXXXXXX',
});
await manager.init();
// Programmatic consent
await manager.acceptAll();
await manager.rejectAll();
await manager.savePreferences({ analytics: true, marketing: false });
// Preference center
manager.showPreferenceCenter();
// Check state
const consent = manager.getConsent();
const isEU = manager.isEUUser();
// Cleanup
manager.destroy();createConsentPlugin({ euDetection: 'auto' })Tries in order:
- Cloudflare
X-Is-EU-Countryheader - IP API (ipapi.co)
- Timezone heuristics fallback
The banner and preference center use CSS custom properties:
:root {
--consent-bg: #ffffff;
--consent-text: #1a1a1a;
--consent-text-secondary: #666666;
--consent-link: #0066cc;
--consent-btn-accept-bg: #0066cc;
--consent-btn-accept-text: #ffffff;
--consent-btn-reject-bg: #e0e0e0;
--consent-btn-reject-text: #1a1a1a;
--consent-font: system-ui, -apple-system, sans-serif;
}Dark mode is automatically supported via prefers-color-scheme.
| Feature | Status |
|---|---|
| Consent banner component | ✅ |
| Preference center modal | ✅ |
| Google Consent Mode v2 | ✅ |
| GDPR compliance | ✅ |
| CCPA compliance | ✅ |
| GDPR roaming protection | ✅ |
| GA4 integration | ✅ |
| GA4 event tracking (ecommerce, conversions) | ✅ |
| EU geo-detection | ✅ |
| Script blocking | ✅ |
| i18n (13 locales) | ✅ |
| Vue 3 / VitePress / Quasar / Nuxt 3 | ✅ |
| Vanilla JS entry point | ✅ |
| UMD/CDN build | ✅ |
| Remote consent storage | ✅ |
| Retry on rate limit (KV storage) | ✅ |
| Dark mode support | ✅ |
| Feature | Description |
|---|---|
| Analytics dashboard | Opt-in rates, banner interactions (via privacy.structured.world) |
- vue-privacy-worker — Cloudflare Worker for server-side consent storage
Apache 2.0 — see LICENSE