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.
<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.
<!-- 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).
<!-- 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.
// 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.
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.
<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>
// 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 |