@polyus-studio/vera-cmp
v0.1.2
Published
Centralized cookie consent management (CMP) for 30+ sites — a typed wrapper around vanilla-cookieconsent v3 with Vue/Vite, Nuxt, React and Next.js adapters, tracker loaders, Google Consent Mode and GPC.
Maintainers
Readme
@polyus-studio/vera-cmp
Centralized cookie-consent management (CMP) for 30+ sites — a typed wrapper
around vanilla-cookieconsent v3 with
first-class adapters for Vue/Vite, Nuxt, React and Next.js
(including SSR).
- 🍪 Four categories:
necessary(read-only),functional,analytics,marketing - 🚫 No optional tracker loads before consent (opt-in / GDPR by default)
- 📊 Built-in loaders for Google Analytics, Google Tag Manager, Meta Pixel, Microsoft Clarity and Hotjar — each disable-able or replaceable per site
- 🟢 Google Consent Mode v2 (
basic/advanced) and Global Privacy Control - ♻️ Consent revision, revoke, and known-cookie erasure
- 🌍 English & Russian copy out of the box, deeply overridable
- 🎨 Responsive theme driven by CSS variables (light/dark)
- 🧱 ESM build, TypeScript declarations, separate CSS export
- 🛡️ Guards against double initialization and duplicate script injection
No domains, company names or tracker ids are hard-coded — everything is passed through config.
Installation
The package is published publicly on the npm registry. No custom registry configuration or authentication is required to install it:
npm install @polyus-studio/vera-cmpQuick start (framework-agnostic)
import {
initPrivacyConsent,
openPrivacyPreferences,
resetPrivacyConsent,
} from '@polyus-studio/vera-cmp'
import '@polyus-studio/vera-cmp/style.css'
initPrivacyConsent({
siteName: 'Acme',
language: 'en',
revision: 1,
links: {
privacyPolicy: '/privacy',
cookiePolicy: '/cookies',
terms: '/terms',
},
trackers: {
googleAnalytics: { id: 'G-XXXXXXX' },
metaPixel: { id: '000000000000000' },
},
})
// Re-open the preferences dialog (e.g. from a "Cookie settings" footer link):
openPrivacyPreferences()
// Revoke consent, erase cookies and reload:
resetPrivacyConsent()The three functions are the entire public API:
| Function | Description |
| --- | --- |
| initPrivacyConsent(config) | Initialize the banner. Returns a controller with { ready, openPreferences, reset, acceptCategory, getConsent }. SSR-safe (no-op on the server) and idempotent. |
| openPrivacyPreferences() | Open the preferences modal. |
| resetPrivacyConsent(options?) | Revoke consent → deny Consent Mode, erase known cookies, tear down, then reload (default) or re-run the saved config ({ reload: false }). |
Framework integration
Vite + Vue
// main.ts
import { createApp } from 'vue'
import { createPrivacyConsent } from '@polyus-studio/vera-cmp/vue'
import '@polyus-studio/vera-cmp/style.css'
import App from './App.vue'
createApp(App)
.use(
createPrivacyConsent({
siteName: 'Acme',
links: { privacyPolicy: '/privacy', cookiePolicy: '/cookies' },
trackers: { googleAnalytics: { id: 'G-XXXXXXX' } },
}),
)
.mount('#app')<script setup lang="ts">
import { usePrivacyConsent } from '@polyus-studio/vera-cmp/vue'
const { openPreferences } = usePrivacyConsent()
</script>
<template>
<button @click="openPreferences()">Cookie settings</button>
</template>Initialization is client-only, so it is safe with Vite SSR.
Nuxt
Register the module and pass options under the veraCmp key:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@polyus-studio/vera-cmp/nuxt'],
css: ['@polyus-studio/vera-cmp/style.css'],
veraCmp: {
siteName: 'Acme',
language: 'en',
links: { privacyPolicy: '/privacy', cookiePolicy: '/cookies' },
trackers: { googleTagManager: { id: 'GTM-XXXXXXX' } },
},
})The module installs a client-only plugin (no SSR execution) and auto-imports
the useVeraCmp() composable:
<script setup lang="ts">
const { open, reset } = useVeraCmp()
</script>
<template>
<button @click="open()">Cookie settings</button>
</template>Options are exposed through runtimeConfig.public.veraCmp, so they can be
overridden per environment.
Next.js (App Router)
The /next entry is a client component ("use client"), so it can be dropped
straight into a server-rendered layout:
// app/layout.tsx
import { PrivacyConsentProvider } from '@polyus-studio/vera-cmp/next'
import '@polyus-studio/vera-cmp/style.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<PrivacyConsentProvider
config={{
siteName: 'Acme',
links: { privacyPolicy: '/privacy', cookiePolicy: '/cookies' },
trackers: { googleAnalytics: { id: 'G-XXXXXXX' } },
}}
/>
{children}
</body>
</html>
)
}'use client'
import { usePrivacyConsent } from '@polyus-studio/vera-cmp/next'
export function CookieSettingsButton() {
const { openPreferences } = usePrivacyConsent()
return <button onClick={() => openPreferences()}>Cookie settings</button>
}For plain React (CRA / Vite React) import from @polyus-studio/vera-cmp/react
— the API is identical.
Configuration reference
interface PrivacyConsentConfig {
siteName: string
language?: 'en' | 'ru' // default 'en'
autoDetectLanguage?: boolean // detect from the browser
revision?: number // bump to force re-consent
links: {
privacyPolicy: string
cookiePolicy: string
terms?: string
}
cookie?: { // consent cookie storage
name?: string // default 'vera_cmp_consent'
domain?: string
path?: string
sameSite?: 'Lax' | 'Strict' | 'None'
expiresAfterDays?: number // default 182
}
manageScriptTags?: boolean // legacy <script type="text/plain"> mode, default false
allowDirectGaWithGtm?: boolean // silence the GA+GTM warning, default false
consentMode?: {
enabled?: boolean // default true
mode?: 'basic' | 'advanced' // default 'basic'
}
gpc?: {
enabled?: boolean // default true
deniedCategories?: Category[] // default ['marketing']
lockDeniedCategories?: boolean // default true
showHonoredNotice?: boolean // default true
}
trackers?: {
googleAnalytics?: TrackerSettings // → analytics
googleTagManager?: TrackerSettings // → analytics (consent-aware container)
metaPixel?: TrackerSettings // → marketing
clarity?: TrackerSettings // → analytics
hotjar?: TrackerSettings & { version?: number } // → analytics
}
guiOptions?: GuiOptions // passed to vanilla-cookieconsent
onFirstConsent?, onConsent?, onChange? // lifecycle hooks
translations?: { en?: DeepPartial<Translation>; ru?: DeepPartial<Translation> }
}
interface TrackerSettings {
id?: string
enabled?: boolean // default true when an id is set
category?: Category // override the gating category
loader?: (ctx) => void | Promise<void> // hybrid: custom loader
}Trackers (hybrid loading)
By default each configured tracker is loaded by a built-in loader only after the relevant category is accepted, and its script is injected exactly once (duplicate injections are guarded against). Per tracker you can:
- disable the built-in loader:
googleAnalytics: { enabled: false } - replace it with your own:
metaPixel: { id: '…', loader: (ctx) => { /* … */ } }
trackers: {
googleAnalytics: { id: 'G-XXXXXXX' },
hotjar: { id: '1234567', version: 6 },
clarity: { id: 'abcdefghij' },
// Load your own script instead of the built-in one:
metaPixel: {
id: '000000000000000',
loader: () => {/* custom injection */},
},
}Google Consent Mode
basic(default) — Google tags are not loaded or contacted before consent. On acceptance the tag is loaded andconsentis updated.advanced— the Google tag loads immediately with all signals denied and useswait_for_updateto send cookieless pings until the user decides.
consentMode: { mode: 'advanced' }Google Tag Manager
GTM is treated as a consent-aware container, not a plain tracker: tags inside
it are expected to respect Consent Mode / their own trigger conditions. If you
enable both direct Google Analytics and GTM, a warning is emitted (GA is
usually deployed through GTM, so running both double-counts hits). Set
allowDirectGaWithGtm: true if this is intentional.
Global Privacy Control (GPC)
When the browser sends a GPC signal, the categories in gpc.deniedCategories
(default ['marketing']) are turned off — even if the stored consent
previously accepted them (an active GPC signal overrides old consent). The
banner still shows so the visitor can manage the remaining categories; denied
categories are locked and a notice explains that GPC was honoured.
Revoke & revision
- Bump
revisionafter a policy change to invalidate stored consent and re-prompt. resetPrivacyConsent()denies Consent Mode, erases known first-party cookies, tears the plugin down and reloads (pass{ reload: false }to re-run in place).
Theming
Import the stylesheet once and override any --vera-cmp-* custom property:
:root {
--vera-cmp-accent: #e2231a;
--vera-cmp-accent-hover: #b91b14;
--vera-cmp-radius: 8px;
--vera-cmp-font-family: 'Inter', system-ui, sans-serif;
}Light and dark themes are handled automatically via prefers-color-scheme;
force one with <html data-vera-cmp-theme="dark">.
Development
npm install
npm run typecheck # tsc --noEmit
npm run test:run # vitest
npm run build # dist/*.js, *.d.ts and dist/vera-cmp.css
npm run test:fixtures # build the Vite/Next/Nuxt consumer fixtures against the packed package
npm run release:check # all checks above plus npm pack --dry-runCI runs typecheck + unit tests + build, then builds the consumer fixtures.
Until npm Trusted Publishing is configured, verify a release with
npm run release:check and publish it manually from an npm account that belongs
to the polyus-studio organization and has two-factor authentication enabled:
npm login
npm publishpublishConfig fixes the destination to the public npm registry and the package
access level to public.
License
MIT © Polyus Studio
