Utility Cookie Consent

Cookie Consent

A self-contained EU GDPR and ePrivacy compliant cookie consent banner. The component generates and manages its own fixed-position DOM, stores the user's granular consent as a JSON cookie readable by PHP, and optionally activates blocked third-party scripts once consent is granted. All UI strings go through the framework's i18n system.

Drop a single element into your layout template; the banner appears automatically on first visit. Users can accept all, reject non-essential cookies, or open an inline preferences panel with per-category toggles.

Quick Start

Place one element in your layout template immediately after the opening <body> tag. The component builds and appends the banner DOM to document.body automatically — no additional markup needed.

PHP / HTML
<body>

<!-- Cookie consent banner — place once, right after <body> -->
<div data-wf-cookie-consent
     data-wf-categories='["analytics","marketing","preferences"]'
     data-wf-cookie-name="wf_consent"
     data-wf-position="bottom">
</div>

<!-- Rest of your layout... -->

Position Variants

Three positions are available via data-wf-position. The default bottom variant is a full-width bar — the most common EU-compliant pattern. The card variants float in a corner and suit lighter disclosure needs.

HTML
<!-- Full-width bar at bottom (default) -->
<div data-wf-cookie-consent data-wf-position="bottom"></div>

<!-- Floating card, bottom-left corner -->
<div data-wf-cookie-consent data-wf-position="bottom-left"></div>

<!-- Floating card, bottom-right corner -->
<div data-wf-cookie-consent data-wf-position="bottom-right"></div>

Script Blocking

Tag any non-essential <script> elements with type="text/plain" and data-wf-consent-category. The browser ignores text/plain scripts entirely; the component re-executes them as live scripts when the matching category is granted. Once activated, a script is never re-executed on subsequent page loads (the stored cookie triggers activation immediately on DOMContentLoaded).

HTML
<!-- Only executes after analytics consent is granted -->
<script type="text/plain" data-wf-consent-category="analytics">
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
</script>

<!-- Only executes after marketing consent is granted -->
<script type="text/plain" data-wf-consent-category="marketing">
    !function(f,b,e,v,n,t,s) {
        /* Meta Pixel initialization */
    }(window, document,'script','https://connect.facebook.net/en_US/fbevents.js');
    fbq('init', 'XXXXXXXXXXXXXXXXX');
</script>

PHP Server-Side Reading

The consent is stored as a JSON cookie. PHP can read it on any subsequent request to conditionally render server-side scripts, skip personalization, or log audit records.

PHP
// Read consent cookie (safe default: empty array)
$consent = json_decode(stripslashes($_COOKIE['wf_consent'] ?? '{}'), true);
$granted  = $consent['granted']  ?? [];
$rejected = $consent['rejected'] ?? [];
$ts       = $consent['ts']       ?? null;   // ISO 8601 timestamp

// Check a specific category
$hasAnalytics   = in_array('analytics',   $granted);
$hasMarketing   = in_array('marketing',   $granted);
$hasPreferences = in_array('preferences', $granted);

// Conditionally render a server-side tracking pixel
if ($hasMarketing) {
    echo '<img src="https://track.example.com/pixel.gif" width="1" height="1">';
}

// Cookie format:
// {
//   "v": 1,
//   "ts": "2026-08-04T12:00:00.000Z",
//   "granted":  ["analytics"],
//   "rejected": ["marketing", "preferences"]
// }

JavaScript API

The component instance is stored as el._wfConsent. A static method reads the cookie without needing an element reference, useful in analytics setup code or module entry points.

JS
const el = document.querySelector('[data-wf-cookie-consent]')

// Re-show the banner (e.g. from a "Manage cookies" footer link)
el._wfConsent.show()

// Clear consent cookie and show the banner again
el._wfConsent.reset()

// Read the stored consent object (null if not yet given)
const consent = el._wfConsent.getConsent()
// → { v: 1, ts: '2026-08-04T...', granted: ['analytics'], rejected: [...] }

// Check a single category
el._wfConsent.hasConsent('analytics')   // → true | false

// Static: read consent without a DOM reference
WojoCookieConsent.getConsent('wf_consent')
// → same object as above, or null

// Listen for any consent decision
document.addEventListener('wf:consent-change', e => {
    const { granted, rejected, ts } = e.detail
    console.log('Granted:', granted)
    // Re-initialise analytics, update UI, etc.
})

// "Manage cookies" link pattern (e.g. in a footer)
document.querySelector('#manage-cookies').addEventListener('click', () => {
    document.querySelector('[data-wf-cookie-consent]')._wfConsent.show()
})

Events

All events are dispatched on the host element and bubble. The wf:consent-change event is additionally dispatched on document for global listeners (e.g. in a separate analytics module).

Event Fired on detail
wf:consent-show Host element { categories }
wf:consent-hide Host element {}
wf:consent-accept Host element { v, ts, granted, rejected }
wf:consent-reject Host element { v, ts, granted, rejected }
wf:consent-save Host element { v, ts, granted, rejected }
wf:consent-change Host element + document { v, ts, granted, rejected }

Internationalization

All UI strings are resolved through WojoI18n.t('cookieConsent.*') and fall back to built-in English. Override per-element with data-wf-* attributes (highest priority), or register a locale file to translate globally.

HTML — per-element text overrides
<div data-wf-cookie-consent
     data-wf-title="Nous utilisons des cookies"
     data-wf-body="Choisissez les cookies que vous autorisez ci-dessous."
     data-wf-accept-label="Tout accepter"
     data-wf-reject-label="Tout refuser"
     data-wf-manage-label="Gérer les préférences"
     data-wf-save-label="Enregistrer"
     data-wf-analytics-label="Analytiques"
     data-wf-analytics-desc="Nous aident à comprendre comment les visiteurs utilisent le site."
     data-wf-marketing-label="Marketing"
     data-wf-marketing-desc="Utilisés pour afficher des publicités personnalisées."
     data-wf-preferences-label="Préférences"
     data-wf-preferences-desc="Mémorisent vos paramètres d'affichage.">
</div>
JS — locale registration
// Register a partial locale (deep-merges with built-in English)
WojoI18n.register('fr', {
    cookieConsent: {
        title:            'Nous utilisons des cookies',
        body:             'Choisissez les cookies que vous autorisez ci-dessous.',
        acceptLabel:      'Tout accepter',
        rejectLabel:      'Tout refuser',
        manageLabel:      'Gérer les préférences',
        saveLabel:        'Enregistrer',
        alwaysOn:         'Toujours activé',
        necessaryLabel:   'Nécessaires',
        necessaryDesc:    'Indispensables au fonctionnement du site.',
        analyticsLabel:   'Analytiques',
        analyticsDesc:    'Nous aident à comprendre l\'utilisation du site.',
        marketingLabel:   'Marketing',
        marketingDesc:    'Utilisés pour les publicités personnalisées.',
        preferencesLabel: 'Préférences',
        preferencesDesc:  'Mémorisent vos paramètres de navigation.',
    },
})

Data Attribute Reference

Attribute Default Description
data-wf-cookie-name wf_consent Cookie name written to the browser and readable by PHP via $_COOKIE
data-wf-cookie-days 365 Consent expiry in days (resets whenever the user changes preferences)
data-wf-categories ["analytics","marketing","preferences"] JSON array of category keys. Each key gets its own toggle in the preferences panel
data-wf-position bottom bottom | bottom-left | bottom-right
data-wf-auto-show true Show the banner automatically on page load when no consent is stored
data-wf-show-delay 300 Milliseconds to wait before animating the banner in (lets the page settle)
data-wf-block-scripts true When true, activates type="text/plain" scripts tagged with data-wf-consent-category after consent is granted
data-wf-title i18n Banner heading text
data-wf-body i18n Banner body text
data-wf-accept-label i18n "Accept all" button label
data-wf-reject-label i18n "Reject non-essential" button label
data-wf-manage-label i18n "Manage preferences" button label
data-wf-save-label i18n "Save preferences" button label (inside the preferences panel)
data-wf-necessary-label i18n Label for the always-on Necessary row
data-wf-necessary-desc i18n Description for the always-on Necessary row
data-wf-{category}-label i18n Label for a specific category, e.g. data-wf-analytics-label
data-wf-{category}-desc i18n Description for a specific category, e.g. data-wf-analytics-desc