CookieConsent
CookieConsent
A drop-in consent flow for the entire site. The banner reads app.config.xAffiliate.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 useXAFConsentTracking() composable and xaf-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).
Mirrors the nuxt-x-marketing XMarkPrivacyCookieConsent pattern — same composable shape, same storage convention (xAffiliate.consent vs xMarketing.consent), same plugin bootstrap.
Components
<XAFCookieConsent />
<XAFCookieConsent
:policy-url="legalBase + '/cookie-policy'"
:privacy-url="legalBase + '/privacy-policy'"
/>
The component is auto-teleported to <body> 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({
xAffiliate: {
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({
xAffiliate: {
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 | 'xAffiliate.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 the default app/layouts/default.vue:
<template>
<div>
<slot />
<XAFCookieConsent
:policy-url="legalBase + '/cookie-policy'"
:privacy-url="legalBase + '/privacy-policy'"
/>
</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>
<XAFCookieConsent :force-show="showConsent" />
</template>
useXAFConsentTracking() composable
Underlying script-injection engine for <XAFCookieConsent />. 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 = useXAFConsentTracking()
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.
xaf-consent-tracking.client.ts plugin
Auto-registered by the layer. Bootstraps useXAFConsentTracking() 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/xaf-consent-tracking.client.ts and is picked up by Nuxt auto-import.
Storage key
The default storage key is xAffiliate.consent. Override via the storageKey prop if you ship multiple consent UIs (e.g. one for the affiliate layer, one for the marketing layer — they share a browser but use different keys).
Cookie → localStorage migration (v0.5.0)
Prior to v0.5.0, <XAFCookieConsent /> stored consent in a document.cookie value (default name xaf_consent). v0.5.0 switches to localStorage (default key xAffiliate.consent) to align with nuxt-x-marketing's XMarkPrivacyCookieConsent and to make the storage inspectable from dev tools.
If a consuming site had previously-deployed consent decisions under the old cookie-based storage, those users will see the banner again on their next visit (one-time regression; no manual migration needed). New visitors get the consent flow as expected.
AI Context
category: Privacy
package: "@xenterprises/nuxt-x-affiliate"
components:
- XAFCookieConsent
composables:
- useXAFConsentTracking
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.xAffiliate.tracking (gtmId / ga4Id / clarityId convenience
fields, or scripts[] for anything else), mount <XAFCookieConsent /> once in
your layout, 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: xAffiliate.consent (default)
