Free tool
Generator for a cookie banner
Configure your banner and get a self-contained HTML/CSS/JS snippet (no dependency) to paste into your site, whatever its framework.
Indicative template, not legal advice. Adapt the wording and the categories to what your site actually does (only tick a category if you genuinely set that kind of cookie) before going live.
The banner alone is not enough.It stores the user's choice (accepted/refused, expiring after 6 months) and fires a zkyCookieConsentChanged event, it is up to you to hook your third-party scripts (Google Analytics, advertising pixels, etc.) onto it so that they only load after consent. Without that wiring, the banner is displayed but blocks nothing on its own.
Generated snippet
<!-- Bandeau cookies généré sur zkysolutions.com/generateur-bandeau-cookies -->
<!-- Vanilla JS, aucune dépendance. Adapter le texte/les catégories à votre cas réel avant mise en ligne. -->
<style>
.zky-cc { position: fixed; inset: auto 0 0 0; z-index: 9999; background: #fff; border-top: 1px solid #e2e8f0; padding: 16px; box-shadow: 0 -4px 24px rgba(0,0,0,.08); font: 14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif; color: #1e293b; }
.zky-cc-inner { max-width: 1100px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; }
.zky-cc a { color: #0C6E84; text-decoration: underline; }
.zky-cc-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.zky-cc-btn { border: 1px solid #cbd5e1; background: #fff; color: #1e293b; padding: 8px 16px; border-radius: 6px; font-weight: 600; cursor: pointer; font-size: 14px; }
.zky-cc-btn-primary { background: #0C6E84; border-color: #0C6E84; color: #fff; }
.zky-cc-settings { display: none; }
.zky-cc-row { display: flex; justify-content: space-between; gap: 16px; border: 1px solid #e2e8f0; border-radius: 6px; padding: 12px; margin-bottom: 8px; }
.zky-cc-row-title { font-weight: 600; margin: 0; }
.zky-cc-row-desc { margin: 4px 0 0; color: #64748b; }
</style>
<div id="zky-cc-banner" class="zky-cc" role="dialog" aria-label="Cookie settings" style="display:none">
<div class="zky-cc-inner">
<div class="zky-cc-main">
<p style="margin:0">
Ce site uses cookies that are strictly necessary for it to work.
<a href="/confidentialite">Learn more</a>.
</p>
<div class="zky-cc-settings"><p style="margin:0;color:#64748b">No optional category configured : only strictly necessary cookies.</p></div>
</div>
<div class="zky-cc-actions">
<button type="button" class="zky-cc-btn" data-zky-cc-action="settings">Customise</button>
<!-- "Refuser" et "Accepter" ont volontairement le même style : la CNIL
considère qu'un bouton "accepter" visuellement plus mis en avant que
"refuser" est un dark pattern (consentement pas vraiment libre). -->
<button type="button" class="zky-cc-btn zky-cc-btn-primary" data-zky-cc-action="refuse">Reject all</button>
<button type="button" class="zky-cc-btn zky-cc-btn-primary" data-zky-cc-action="accept">Accept all</button>
<button type="button" class="zky-cc-btn zky-cc-btn-primary" data-zky-cc-action="save" style="display:none">Save</button>
</div>
</div>
</div>
<script>
(function () {
var STORAGE_KEY = 'zky_cookie_consent';
// Recommandation CNIL : le consentement n'est pas éternel, ~6 mois avant de
// redemander (au-delà, considérer que la situation/l'utilisateur a pu changer).
var CONSENT_MAX_AGE_MS = 180 * 24 * 60 * 60 * 1000;
var banner = document.getElementById('zky-cc-banner');
var settingsBox = banner.querySelector('.zky-cc-settings');
var saveBtn = banner.querySelector('[data-zky-cc-action="save"]');
var checkboxes = banner.querySelectorAll('[data-zky-cc-category]');
function getConsent() {
try {
var stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (!stored || !stored.consentedAt) return null;
if (Date.now() - stored.consentedAt > CONSENT_MAX_AGE_MS) return null;
return stored;
} catch (e) { return null; }
}
function setConsent(consent) {
consent.consentedAt = Date.now();
localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
banner.style.display = 'none';
document.dispatchEvent(new CustomEvent('zkyCookieConsentChanged', { detail: consent }));
}
function buildConsent(allValue) {
var consent = { necessary: true };
checkboxes.forEach(function (cb) {
consent[cb.getAttribute('data-zky-cc-category')] = allValue !== undefined ? allValue : cb.checked;
});
return consent;
}
banner.addEventListener('click', function (e) {
var action = e.target.getAttribute('data-zky-cc-action');
if (!action) return;
if (action === 'accept') setConsent(buildConsent(true));
else if (action === 'refuse') setConsent(buildConsent(false));
else if (action === 'settings') {
settingsBox.style.display = 'block';
saveBtn.style.display = 'inline-block';
} else if (action === 'save') setConsent(buildConsent());
});
window.zkyOpenCookieSettings = function () {
banner.style.display = 'block';
settingsBox.style.display = 'block';
saveBtn.style.display = 'inline-block';
};
if (!getConsent()) banner.style.display = 'block';
})();
</script>