PrivacyCookieConsent
PrivacyCookieConsent
A drop-in consent flow for the entire site. The banner reads app.config.xMarketing.tracking, shows on first visit, persists the visitor's choice to localStorage, and dynamically injects GTM / GA4 / Clarity / Meta Pixel / Hotjar / Segment / LinkedIn Insight / etc. once the matching consent category is granted.
The companion useConsentTracking() composable and consent-tracking.client.ts plugin handle the script injection and rehydrate-on-revisit flow so returning visitors get their tracking scripts loaded immediately (no flash of banner).
Components
<XMarkPrivacyCookieConsent />
<XMarkPrivacyCookieConsent
policy-url="/cookies"
privacy-url="/privacy"
/>
Drop it into app.vue (the layer's default app.vue already does this). The banner is bottom-anchored and renders a preferences modal via UModal when the visitor clicks "Customize".
Convenience configuration (90% of sites)
// app.config.ts — drop in your tracking IDs
export default defineAppConfig({
xMarketing: {
tracking: {
gtmId: 'GTM-XXXXXXX',
ga4Id: 'G-XXXXXXXX',
clarityId: 'abc123def4',
},
},
})
When gtmId is set, the layer auto-injects GTM via https://www.googletagmanager.com/gtm.js. When GTM is present, the layer skips the standalone GA4 / Clarity snippets to avoid double-counting page views (GTM manages them). Set tracking.ga4Id or tracking.clarityId only when you're NOT using GTM.
Escape hatch — custom scripts
For everything else (Meta Pixel, Hotjar, Segment, LinkedIn Insight, Pinterest, TikTok, etc.), use the scripts[] array. Each entry fires once the matching category is granted:
export default defineAppConfig({
xMarketing: {
tracking: {
scripts: [
{
id: 'meta-pixel',
src: 'https://connect.facebook.net/en_US/fbevents.js',
category: 'marketing',
attrs: { async: '' },
},
{
id: 'meta-pixel-init',
inline: 'fbq("init", "1234567890"); fbq("track", "PageView");',
category: 'marketing',
},
{
id: 'hotjar',
src: 'https://static.hotjar.com/c/hotjar-1234567.js?sv=6',
category: 'analytics',
attrs: { async: '' },
},
],
},
},
})
src and inline are mutually supportive — set src for an external script, inline for a hand-written snippet like a gtag('js', new Date()) call. attrs map to attributes on the injected <script> tag (use "" for boolean attributes like async).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'We use cookies' | Banner heading. |
message | string | (see source) | Banner body text. |
acceptLabel | string | 'Accept all' | Accept button text. |
rejectLabel | string | 'Reject all' | Reject button text. |
saveLabel | string | 'Save preferences' | Save button text (modal). |
policyLabel | string | 'Read our policy' | Policy link text. |
policyUrl | string | — | Cookie policy URL. |
privacyUrl | string | — | Privacy policy URL. |
prefsTitle | string | 'Cookie preferences' | Modal title. |
prefsDescription | string | (see source) | Modal description. |
showCustomize | boolean | true | Show the Customize button in the banner. |
categories | Category[] | 4 standard | Categories shown in the modal — [{ id, label, description, required?, enabledByDefault? }]. |
storageKey | string | 'xMarketing.consent' | localStorage key for the consent state. |
forceShow | boolean | false | Force the banner open (e.g. from a "Manage cookies" link in the footer). |
Emits
| Event | Payload | When |
|---|---|---|
accept | Record<CategoryId, boolean> | Visitor accepted all categories. |
reject | Record<CategoryId, boolean> | Visitor rejected all non-required categories. |
save | Record<CategoryId, boolean> | Visitor saved custom preferences. |
Categories
The four standard categories — necessary (always-on, locked), analytics (GTM/GA4/Clarity), marketing (Meta Pixel/AdWords), preferences (UI settings) — are designed to map 1:1 to the scripts[].category field. Add or remove categories by passing a custom categories prop.
Mounting
The component is auto-teleported to <body> and renders a <UModal> for the preferences. Wire it into your layout once — usually in app.vue after the main <slot />:
<template>
<div>
<NuxtPage />
<XMarkPrivacyCookieConsent policy-url="/cookies" privacy-url="/privacy" />
</div>
</template>
The "Manage cookies" footer link
Use forceShow with a ref to re-open the banner from anywhere in your app:
<script setup>
const showConsent = ref(false)
</script>
<template>
<footer>
<UButton variant="link" @click="showConsent = true">Manage cookies</UButton>
</footer>
<XMarkPrivacyCookieConsent :force-show="showConsent" />
</template>
useConsentTracking() composable
Underlying script-injection engine for <XMarkPrivacyCookieConsent />. Most sites don't need to call it directly — but if you want to programmatically grant consent (e.g. for a "preferences saved" toast that re-fires scripts) or read the current state:
const consent = useConsentTracking()
consent.acceptAll() // grant every category
consent.rejectAll() // grant only necessary
consent.hasConsent('analytics') // boolean — has analytics consent?
consent.state.value // { necessary, analytics, marketing, preferences }
consent.setConsent({ // full state map
necessary: true,
analytics: true,
marketing: false,
preferences: true,
})
The composable listens for the consent:updated window event, so any banner UI that fires it stays in sync with the script loader.
consent-tracking.client.ts plugin
Auto-registered by the layer. Bootstraps useConsentTracking() on client mount:
- Reads stored consent from
localStorage. - If consent was previously granted, immediately injects the matching scripts.
- Registers a
consent:updatedlistener for future banner interactions.
You don't need to install it manually — it ships in app/plugins/consent-tracking.client.ts and is picked up by Nuxt auto-import.
Storage key
The default storage key is xMarketing.consent. Override via the storageKey prop if you ship multiple consent UIs (e.g. one for the marketing layer, one for the affiliate layer — they share a browser but use different keys).
Difference from XMarkPrivacyCookieBanner + XMarkPrivacyGDPR
The existing XMarkPrivacyCookieBanner (bottom banner with accept/reject) and XMarkPrivacyGDPR (granular preferences modal) are still shipped and untouched. They:
- Use
gdpr-preferencesandcookie-consentstorage keys. - Emit events but don't auto-fire tracking scripts.
XMarkPrivacyCookieConsent is the consent-aware variant — same UI primitives, but it wires into useConsentTracking() and the configured tracking IDs so the whole pipeline (banner → consent → script injection) works from a single <XMarkPrivacyCookieConsent /> mount. Use it for new sites; the older components remain for sites that want finer control without the auto-tracking flow.
AI Context
category: Privacy
package: "@xenterprises/nuxt-x-marketing"
components:
- XMarkPrivacyCookieConsent
composables:
- useConsentTracking
use-when: >
Default consent flow for any site shipping GTM / GA4 / Clarity / Meta Pixel /
Hotjar / Segment / LinkedIn Insight / etc. Drop tracking IDs into
app.config.xMarketing.tracking (gtmId / ga4Id / clarityId convenience
fields, or scripts[] for anything else), mount <XMarkPrivacyCookieConsent />
once in app.vue, and the banner + script injection handles itself.
typical-page-section: Bottom-anchored consent banner + preferences modal — mounted once per site, not per page.
scripts-fire-on: consent:updated window event + first revisit when storage key already set
storage-key: xMarketing.consent (default)
SectionStitch
SVG section divider with 10 shape variants (diagonal, slant, concave, convex, tilt, waves, clouds, peaks, scallop, arrow), configurable colors, height, glow line, and directional flips.
Section
Configurable section wrapper with background variants (default/subtle/elevated/bold/transparent), padding scales, container sizing, and optional image/pattern backgrounds with parallax support.
