X Enterprises
nuxt-x-marketing

PrivacyCookieConsent

Consent-aware cookie banner + preferences modal that auto-fires configured tracking scripts (GTM / GA4 / Clarity / Meta Pixel / Hotjar / etc.) only after the visitor grants consent. Drop tracking IDs into app.config.xMarketing.tracking and the banner handles the rest.

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

PropTypeDefaultDescription
titlestring'We use cookies'Banner heading.
messagestring(see source)Banner body text.
acceptLabelstring'Accept all'Accept button text.
rejectLabelstring'Reject all'Reject button text.
saveLabelstring'Save preferences'Save button text (modal).
policyLabelstring'Read our policy'Policy link text.
policyUrlstringCookie policy URL.
privacyUrlstringPrivacy policy URL.
prefsTitlestring'Cookie preferences'Modal title.
prefsDescriptionstring(see source)Modal description.
showCustomizebooleantrueShow the Customize button in the banner.
categoriesCategory[]4 standardCategories shown in the modal — [{ id, label, description, required?, enabledByDefault? }].
storageKeystring'xMarketing.consent'localStorage key for the consent state.
forceShowbooleanfalseForce the banner open (e.g. from a "Manage cookies" link in the footer).

Emits

EventPayloadWhen
acceptRecord<CategoryId, boolean>Visitor accepted all categories.
rejectRecord<CategoryId, boolean>Visitor rejected all non-required categories.
saveRecord<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>

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.


Auto-registered by the layer. Bootstraps useConsentTracking() on client mount:

  1. Reads stored consent from localStorage.
  2. If consent was previously granted, immediately injects the matching scripts.
  3. Registers a consent:updated listener 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-preferences and cookie-consent storage 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)
Copyright © 2026