Générateur de bandeau cookies auto-hébergé

Répondez à quelques questions et obtenez un bandeau de consentement aux cookies que vous possédez : du HTML, CSS et JavaScript natif pur à coller directement dans votre site. Il bloque réellement les scripts non essentiels jusqu'au consentement, stocke le choix dans localStorage, et place un bouton « Tout refuser » aussi visible que « Tout accepter » - le détail que des régulateurs comme la CNIL française exigent précisément. Aucune plateforme de consentement tierce, aucun compte, aucune limite de pages vues.

Tout est généré dans votre navigateur. Le code que vous obtenez n'effectue lui-même aucune requête réseau - pas de CDN, pas de serveur de gestion du consentement, pas de compteur de pages vues à épuiser.

Affiché dans le texte de la bannière - les visiteurs doivent savoir qui leur demande.

Obligatoire. Sans elle, le consentement n'est pas éclairé.

Catégories de cookies que vous définissez

Toujours

Strictly necessary

Exempté de consentement, donc toujours présent et ne peut pas être désactivé.

La CNIL considère environ 6 mois comme une bonne pratique, pour un refus comme pour un consentement.

Exposée en tant que window.cookieConsent et stockée sous "cookieConsent.v1".

Deux choses que vous ne pouvez pas désactiver ici

« Tout refuser » s'affiche toujours à côté de « Tout accepter », à la même taille et avec la même importance, au premier niveau de la bannière - et les catégories facultatives sont toujours générées décochées. Ce sont les deux détails pour lesquels les régulateurs sanctionnent réellement les sites, donc il n'y a volontairement aucune option pour les enfreindre.

  • À corrigerAdd your site or company name - the banner has to say who is asking.

Aperçu

We use cookies

This site uses cookies that are strictly necessary to run the site. With your permission we would also like to set optional cookies. You can accept them all, refuse them all, or choose category by category.

Accept allReject all
<style>
/* Cookie consent banner - generated by usezivo.com.
   Dependency-free: no fonts, no CDN, no network requests of its own. */

.cc-cookieconsent-root { position: fixed; z-index: 2147483000; }

.cc-cookieconsent-backdrop {
  position: fixed; inset: 0; z-index: 2147482999;
  background: rgba(15, 23, 42, 0.45);
}

.cc-cookieconsent-banner {
  position: fixed;
  left: 0; right: 0; bottom: 0; max-width: none; border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0;
  box-sizing: border-box;
  padding: 18px 20px;
  background: #ffffff;
  color: #0f172a;
  border: 1px solid #e2e8f0;
  border-radius: 14px;
  box-shadow: 0 10px 40px rgba(15, 23, 42, 0.18);
  font: 400 14px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.cc-cookieconsent-title { margin: 0 0 6px; font-size: 15px; font-weight: 700; }
.cc-cookieconsent-text { margin: 0 0 14px; color: #475569; }
.cc-cookieconsent-text a { color: #2563eb; text-decoration: underline; }

/* Accept and Reject are deliberately the SAME component: same size, same
   weight, same visual prominence, side by side at the first level. */
.cc-cookieconsent-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.cc-cookieconsent-btn {
  appearance: none;
  flex: 0 0 auto;
  min-width: 148px;
  padding: 11px 18px;
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  border-radius: 10px;
  border: 1px solid #2563eb;
  background: #2563eb;
  color: #ffffff;
  cursor: pointer;
}
.cc-cookieconsent-btn:hover { filter: brightness(0.94); }
.cc-cookieconsent-btn:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }
.cc-cookieconsent-btn--secondary { background: transparent; color: #2563eb; }
.cc-cookieconsent-link {
  appearance: none; background: none; border: 0; padding: 8px 4px;
  font: inherit; color: #475569; text-decoration: underline; cursor: pointer;
}
.cc-cookieconsent-link:hover { color: #0f172a; }

.cc-cookieconsent-panel {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  box-sizing: border-box;
  width: min(620px, calc(100vw - 32px));
  max-height: min(760px, calc(100vh - 32px));
  overflow-y: auto;
  padding: 22px;
  background: #ffffff; color: #0f172a;
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.3);
  font: 400 14px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.cc-cookieconsent-panel h2 { margin: 0 0 6px; font-size: 17px; }
.cc-cookieconsent-panel-intro { margin: 0 0 16px; color: #475569; }

.cc-cookieconsent-cat {
  padding: 14px 0;
  border-top: 1px solid #e2e8f0;
}
.cc-cookieconsent-cat-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cc-cookieconsent-cat-name { font-weight: 600; }
.cc-cookieconsent-cat-desc { margin: 6px 0 0; color: #64748b; font-size: 13px; }
.cc-cookieconsent-always { font-size: 12px; font-weight: 600; color: #16a34a; }

/* Switch */
.cc-cookieconsent-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex: 0 0 auto; }
.cc-cookieconsent-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.cc-cookieconsent-slider {
  position: absolute; inset: 0; border-radius: 999px;
  background: #cbd5e1; transition: background 0.15s ease; pointer-events: none;
}
.cc-cookieconsent-slider::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px; border-radius: 50%; background: #fff;
  transition: transform 0.15s ease;
}
.cc-cookieconsent-switch input:checked + .cc-cookieconsent-slider { background: #2563eb; }
.cc-cookieconsent-switch input:checked + .cc-cookieconsent-slider::after { transform: translateX(20px); }
.cc-cookieconsent-switch input:focus-visible + .cc-cookieconsent-slider { outline: 3px solid #2563eb; outline-offset: 2px; }

.cc-cookieconsent-panel-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; padding-top: 16px; border-top: 1px solid #e2e8f0; }

@media (max-width: 520px) {
  .cc-cookieconsent-banner { left: 8px; right: 8px; bottom: 8px; max-width: none; border-radius: 14px; border: 1px solid #e2e8f0; transform: none; top: auto; }
  .cc-cookieconsent-btn { flex: 1 1 100%; min-width: 0; }
}

@media (prefers-color-scheme: dark) {
  .cc-cookieconsent-banner, .cc-cookieconsent-panel { background: #0f172a; color: #e2e8f0; border-color: #334155; }
  .cc-cookieconsent-text, .cc-cookieconsent-panel-intro, .cc-cookieconsent-cat-desc { color: #94a3b8; }
  .cc-cookieconsent-cat, .cc-cookieconsent-panel-actions { border-color: #334155; }
  .cc-cookieconsent-link { color: #94a3b8; }
  .cc-cookieconsent-link:hover { color: #e2e8f0; }
  .cc-cookieconsent-slider { background: #475569; }
  @media (max-width: 520px) { .cc-cookieconsent-banner { border-color: #334155; } }
}
</style>

<!-- Cookie consent banner - generated by usezivo.com. Paste before </body>. -->
<div id="cc-cookieconsent-banner" class="cc-cookieconsent-banner" role="dialog" aria-modal="false" aria-labelledby="cc-cookieconsent-title" hidden>
  <p class="cc-cookieconsent-title" id="cc-cookieconsent-title">We use cookies</p>
  <p class="cc-cookieconsent-text">This site uses cookies that are strictly necessary to run the site. With your permission we would also like to set optional cookies. You can accept them all, refuse them all, or choose category by category. <a href="/privacy">Privacy policy</a></p>
  <div class="cc-cookieconsent-actions">
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="accept">Accept all</button>
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="reject">Reject all</button>
    <button type="button" class="cc-cookieconsent-link" data-cc-cookieconsent-action="open">Customise</button>
  </div>
</div>

<div id="cc-cookieconsent-panel" class="cc-cookieconsent-panel" role="dialog" aria-modal="true" aria-labelledby="cc-cookieconsent-panel-title" hidden>
  <h2 id="cc-cookieconsent-panel-title">Cookie preferences</h2>
  <p class="cc-cookieconsent-panel-intro">Strictly necessary cookies cannot be switched off. Every other category is off until you turn it on.</p>
      <div class="cc-cookieconsent-cat">
        <div class="cc-cookieconsent-cat-head">
          <span class="cc-cookieconsent-cat-name">Strictly necessary</span>
          <span class="cc-cookieconsent-always">Always active</span>
        </div>
        <p class="cc-cookieconsent-cat-desc">Needed for the site to work: security, load balancing, remembering what is in your basket and storing this cookie choice. These are exempt from consent.</p>
      </div>
      <div class="cc-cookieconsent-cat">
        <div class="cc-cookieconsent-cat-head">
          <span class="cc-cookieconsent-cat-name">Analytics</span>
          <label class="cc-cookieconsent-switch">
              <input type="checkbox" data-cc-cookieconsent-cat="analytics" aria-label="Analytics">
              <span class="cc-cookieconsent-slider"></span>
            </label>
        </div>
        <p class="cc-cookieconsent-cat-desc">Help us understand which pages are visited and where people get stuck, so we can improve the site. Measurement only, never used to target advertising.</p>
      </div>
  <div class="cc-cookieconsent-panel-actions">
    <button type="button" class="cc-cookieconsent-btn" data-cc-cookieconsent-action="save">Save my choices</button>
    <button type="button" class="cc-cookieconsent-btn cc-cookieconsent-btn--secondary" data-cc-cookieconsent-action="accept">Accept all</button>
    <button type="button" class="cc-cookieconsent-btn cc-cookieconsent-btn--secondary" data-cc-cookieconsent-action="reject">Reject all</button>
  </div>
</div>

<script>
/* Cookie consent - generated by usezivo.com. Vanilla JS, no dependencies,
   no network requests. Paste after the markup, or load as an external file. */
(function () {
  "use strict";

  var KEY = "cookieConsent.v1";
  var MAX_AGE_MS = 183 * 24 * 60 * 60 * 1000;
  var OPTIONAL = ["analytics"];
  var PREFIX = "cc-cookieconsent";

  var banner = document.getElementById(PREFIX + "-banner");
  var panel = document.getElementById(PREFIX + "-panel");
  var backdrop = null;
  var lastFocus = null;

  // --- storage ------------------------------------------------------------
  // localStorage can throw outright in some privacy modes, so every access is
  // guarded. If it is unavailable the banner simply reappears next visit,
  // which is the safe direction to fail in: nothing gets unblocked.
  function read() {
    try {
      var raw = localStorage.getItem(KEY);
      if (!raw) return null;
      var saved = JSON.parse(raw);
      if (!saved || typeof saved !== "object") return null;
      if (typeof saved.at !== "number" || Date.now() - saved.at > MAX_AGE_MS) return null;
      var out = { necessary: true };
      for (var i = 0; i < OPTIONAL.length; i++) {
        out[OPTIONAL[i]] = saved.categories && saved.categories[OPTIONAL[i]] === true;
      }
      return out;
    } catch (e) {
      return null;
    }
  }

  function write(categories) {
    try {
      localStorage.setItem(KEY, JSON.stringify({ v: 1, at: Date.now(), categories: categories }));
    } catch (e) {
      /* storage blocked - the choice applies to this page load only */
    }
  }

  var state = read();

  // --- script gating ------------------------------------------------------
  // Third-party tags are written as:
  //   <script type="text/plain" data-cookie-category="analytics" src="..."></script>
  // The browser will not execute type="text/plain", so nothing runs until the
  // matching category is granted, at which point the tag is swapped for a real
  // one. Inline tags work the same way.
  function unblock(granted) {
    var pending = document.querySelectorAll('script[type="text/plain"][data-cookie-category]');
    for (var i = 0; i < pending.length; i++) {
      var node = pending[i];
      var category = node.getAttribute("data-cookie-category");
      if (!granted[category]) continue;
      var real = document.createElement("script");
      for (var a = 0; a < node.attributes.length; a++) {
        var attr = node.attributes[a];
        if (attr.name === "type" || attr.name === "data-cookie-category") continue;
        real.setAttribute(attr.name, attr.value);
      }
      real.type = "text/javascript";
      if (!node.src) real.text = node.text;
      node.parentNode.replaceChild(real, node);
    }
  }

  var listeners = [];
  function emit(granted) {
    unblock(granted);
    for (var i = 0; i < listeners.length; i++) {
      try {
        listeners[i](granted);
      } catch (e) {
        /* one bad listener must not break the rest */
      }
    }
    try {
      document.dispatchEvent(new CustomEvent("cookieConsent:change", { detail: granted }));
    } catch (e) {
      /* CustomEvent unsupported - the callback API still works */
    }
  }

  // --- UI -----------------------------------------------------------------
  function showBanner() {
    if (!banner) return;
    banner.hidden = false;
    /* no backdrop configured */
  }

  function hideBanner() {
    if (banner) banner.hidden = true;
    if (backdrop && backdrop.parentNode) {
      backdrop.parentNode.removeChild(backdrop);
      backdrop = null;
    }
  }

  function boxes() {
    return panel ? panel.querySelectorAll("[data-" + PREFIX + "-cat]") : [];
  }

  function openPanel() {
    if (!panel) return;
    lastFocus = document.activeElement;
    // Reflect the stored choice, or leave every optional box OFF when there is
    // no stored choice yet. Nothing is ever pre-ticked.
    var current = read();
    var list = boxes();
    for (var i = 0; i < list.length; i++) {
      var id = list[i].getAttribute("data-" + PREFIX + "-cat");
      list[i].checked = !!(current && current[id]);
    }
    panel.hidden = false;
    var first = panel.querySelector("button, input");
    if (first) first.focus();
    document.addEventListener("keydown", onKeydown);
  }

  function closePanel() {
    if (!panel) return;
    panel.hidden = true;
    document.removeEventListener("keydown", onKeydown);
    if (lastFocus && lastFocus.focus) lastFocus.focus();
  }

  function onKeydown(e) {
    // Escape closes the PREFERENCES panel only. It never dismisses the banner:
    // dismissing without choosing must not be treated as consent.
    if (e.key === "Escape") closePanel();
  }

  function decide(categories) {
    var granted = { necessary: true };
    for (var i = 0; i < OPTIONAL.length; i++) {
      granted[OPTIONAL[i]] = categories[OPTIONAL[i]] === true;
    }
    state = granted;
    write(granted);
    closePanel();
    hideBanner();
    emit(granted);
  }

  function all(value) {
    var out = {};
    for (var i = 0; i < OPTIONAL.length; i++) out[OPTIONAL[i]] = value;
    return out;
  }

  function fromPanel() {
    var out = {};
    var list = boxes();
    for (var i = 0; i < list.length; i++) {
      out[list[i].getAttribute("data-" + PREFIX + "-cat")] = list[i].checked === true;
    }
    return out;
  }

  document.addEventListener("click", function (e) {
    var target = e.target.closest ? e.target.closest("[data-" + PREFIX + "-action]") : null;
    if (!target) return;
    var action = target.getAttribute("data-" + PREFIX + "-action");
    if (action === "accept") decide(all(true));
    else if (action === "reject") decide(all(false));
    else if (action === "save") decide(fromPanel());
    else if (action === "open") openPanel();
    else if (action === "close") closePanel();
  });

  // --- public API ---------------------------------------------------------
  window["cookieConsent"] = {
    /** true if that category has been granted. Always true for "necessary". */
    has: function (category) {
      if (category === "necessary") return true;
      return !!(state && state[category]);
    },
    /** The full granted map, or null if no choice has been made yet. */
    get: function () {
      return state ? JSON.parse(JSON.stringify(state)) : null;
    },
    /** Run cb now (if a choice exists) and on every later change. Returns an unsubscribe fn. */
    onChange: function (cb) {
      listeners.push(cb);
      if (state) {
        try {
          cb(state);
        } catch (e) {
          /* ignore */
        }
      }
      return function () {
        var i = listeners.indexOf(cb);
        if (i > -1) listeners.splice(i, 1);
      };
    },
    /** Wire this to a permanent "Cookie preferences" link in your footer. */
    openPreferences: openPanel,
    /** Forget the stored choice and ask again. Withdrawal must be as easy as consent. */
    reset: function () {
      try {
        localStorage.removeItem(KEY);
      } catch (e) {
        /* ignore */
      }
      state = null;
      showBanner();
    },
  };

  // --- boot ---------------------------------------------------------------
  if (state) {
    // A stored choice: apply it silently, do not nag.
    emit(state);
  } else {
    // No choice yet -> nothing is unblocked, and the banner asks.
    showBanner();
  }
})();
</script>

Le code se met à jour en direct, mais corrigez les éléments ci-dessus avant de le mettre en ligne.

D'où viennent les règles ci-dessus

Ce générateur produit l'interface de la bannière et le mécanisme de stockage du consentement. Ce n'est pas un conseil juridique et cela ne peut pas, à lui seul, rendre un site conforme - ce qui compte réellement, c'est quels traceurs vous utilisez, si vous les avez réellement reliés à la bannière, et ce que dit votre politique de confidentialité. Les règles diffèrent aussi d'un pays à l'autre et évoluent avec le temps. Si les enjeux sont importants, faites-le vérifier par un avocat.

Pourquoi l’auto-hébergement plutôt qu’Axeptio ou Cookiebot

Les plateformes de consentement hébergées sont d’excellents produits, mais leurs offres gratuites sont limitées : dépassez un seuil mensuel de pages vues et la bannière cesse de fonctionner, ou la facturation commence. Elles chargent aussi leur propre JavaScript depuis leur propre domaine à chaque affichage de page, ce qui ajoute une requête tierce à votre site au nom de la vie privée, et une dépendance qui peut changer sans que vous le contrôliez.

Le script que cet outil génère pèse environ 6 Ko, n’a aucune dépendance, ne fait lui-même aucune requête réseau, et vit dans votre propre dépôt où vous pouvez lire chaque ligne. Il n’y a aucun fournisseur dont le quota pourrait s’épuiser, et rien ne communique avec l’extérieur.

Ce que fait la bannièreComment
Bloque les scripts avant le consentementLes scripts tiers sont écrits avec type="text/plain" et un attribut data-cookie-category, que les navigateurs refusent d’exécuter. Lors du consentement, la balise est remplacée par un véritable élément script, et ce n’est qu’alors qu’elle s’exécute.
Refuser est aussi simple qu’accepter« Tout accepter » et « Tout refuser » sont le même composant de bouton - même largeur, même marge intérieure, même épaisseur et même couleur - côte à côte au premier niveau.
Rien n’est précochéLes interrupteurs de préférence générés ne portent jamais d’attribut checked. Jamais.
Le silence n’est pas un consentementFermer ou ignorer la bannière ne stocke rien et ne débloque rien. Seul un clic explicite enregistre un choix.
Retrait possible à tout moment<ns/>.openPreferences() rouvre le panneau, et .reset() oublie le choix - reliez l’un ou l’autre à un lien dans le pied de page.
Se souvient de la réponselocalStorage, avec un horodatage et une durée d’expiration que vous choisissez (6 mois par défaut), afin que les visiteurs ne soient pas sollicités à chaque page.

Questions fréquentes

Comment créer un bandeau cookies conforme CNIL gratuit sans plateforme payante ?

Renseignez le nom de votre site, le lien vers votre politique de confidentialité et les catégories de cookies que vous utilisez réellement, puis copiez le bloc généré dans votre page avant la balise de fermeture body. Vous obtenez du HTML, CSS et JavaScript natifs sans dépendance à Axeptio, Cookiebot, Tarteaucitron ou toute autre plateforme de consentement - donc aucune limite de pages vues à atteindre et aucun script tiers chargé à chaque page.

Bloque-t-il vraiment Google Analytics avant consentement, ou cache-t-il juste le bandeau ?

Il le bloque réellement, mais vous devez faire une chose : changer votre balise de type="text/javascript" à type="text/plain" et ajouter data-cookie-category="analytics". Un navigateur n'exécute pas un script text/plain, donc la balise reste inerte dans la page. Dès que le visiteur accorde cette catégorie, le script est cloné en un véritable élément script et s'exécute.

Pourquoi n'y a-t-il pas d'option pour masquer le bouton Tout refuser ?

Parce que c'est le problème le plus souvent cité des bandeaux non conformes. La CNIL considère qu'il doit être aussi facile de refuser les traceurs que de les accepter, et voit dans un bouton « Tout refuser » au premier niveau, avec la même apparence que « Tout accepter », la manière claire d'y parvenir. Ce générateur produit toujours les deux boutons avec la même classe CSS, donc identiques en taille et en poids.

Cela rend-il mon site légalement conforme au RGPD ?

Non, et méfiez-vous de tout outil qui prétend le contraire. Ceci génère l'interface du bandeau et le mécanisme de stockage du consentement. La conformité dépend aussi des traceurs que vous utilisez réellement, du fait que chacun soit vraiment passé par la barrière, du contenu de vos politiques de confidentialité et de cookies, et de règles qui varient selon le pays et évoluent dans le temps.

Outils similaires