Skip to content

structured-world/vue-privacy

Repository files navigation

@structured-world/vue-privacy

GDPR-compliant cookie consent with Google Consent Mode v2 support for Vue 3, Quasar, VitePress, and plain HTML.

npm version npm downloads bundle size CI TypeScript License

Documentation · GitHub · npm

Features

  • 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

Installation

npm install @structured-world/vue-privacy
# or
yarn add @structured-world/vue-privacy
# or
pnpm add @structured-world/vue-privacy

Quick Start

Vue 3

import { 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>

VitePress

TypeScript users: Add vue-router as 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
---

Quasar

// src/boot/consent.ts
import { boot } from 'quasar/wrappers';
import { consentBoot } from '@structured-world/vue-privacy/quasar';

export default boot(consentBoot({
  gaId: 'G-XXXXXXXXXX',
}));

CDN / Script Tag

<script src="https://unpkg.com/@structured-world/vue-privacy"></script>
<script>
  const manager = VuePrivacy.createConsentManager({
    gaId: 'G-XXXXXXXXXX',
    euDetection: 'auto',
  });
  manager.init();
</script>

Vanilla JS (Non-Vue Projects)

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 });

Configuration

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;
}

Composables

<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>

Script Blocking

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.

Remote Consent Storage

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),
  },
});

Core API (Framework-agnostic)

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();

EU Detection

Auto (Recommended)

createConsentPlugin({ euDetection: 'auto' })

Tries in order:

  1. Cloudflare X-Is-EU-Country header
  2. IP API (ipapi.co)
  3. Timezone heuristics fallback

Styling

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.

Current Features

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

Planned

Feature Description
Analytics dashboard Opt-in rates, banner interactions (via privacy.structured.world)

Related Projects

License

Apache 2.0 — see LICENSE

About

Privacy-first consent & analytics for Vue 3, Nuxt 3, VitePress, and Quasar. GDPR/CCPA compliant with Google Consent Mode v2.

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors