Build a self-contained HTML+CSS+JS cookie consent banner — no framework, no CDN. Exposes window.cookieConsent API.
Tick to default-on (still requires user save). Essential is always required.
<style>
#cc-banner{position:fixed;bottom:0;left:0;right:0;border-top:1px solid var(--cc-border);;background:var(--cc-bg);color:var(--cc-fg);padding:16px;font:14px/1.5 system-ui,-apple-system,sans-serif;box-shadow:0 4px 24px rgba(0,0,0,0.15);z-index:99999;--cc-bg:#ffffff;--cc-fg:#111827;--cc-border:#e5e7eb;--cc-muted:#6b7280;--cc-primary:#2563eb;;display:none}
#cc-banner.cc-show{display:block}
#cc-banner .cc-title{margin:0 0 8px;font-size:16px;font-weight:600}
#cc-banner .cc-msg{margin:0 0 12px;color:var(--cc-muted)}
#cc-banner .cc-msg a{color:var(--cc-primary);text-decoration:underline}
#cc-banner .cc-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}
#cc-banner .cc-btn{padding:8px 16px;border-radius:6px;border:1px solid var(--cc-border);font:inherit;cursor:pointer;background:transparent;color:var(--cc-fg)}
#cc-banner .cc-btn-primary{background:var(--cc-primary);color:#fff;border-color:var(--cc-primary)}
#cc-banner .cc-btn-secondary:hover{background:var(--cc-border)}
#cc-banner .cc-cats{display:none;margin:12px 0;border-top:1px solid var(--cc-border);padding-top:12px}
#cc-banner.cc-expanded .cc-cats{display:block}
#cc-banner .cc-cat{margin-bottom:10px}
#cc-banner .cc-cat-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-weight:500}
#cc-banner .cc-cat-required{font-size:11px;background:var(--cc-border);padding:2px 6px;border-radius:4px;color:var(--cc-muted)}
#cc-banner .cc-cat-desc{margin:4px 0 0 24px;font-size:12px;color:var(--cc-muted)}
@media (prefers-color-scheme:dark){#cc-banner{--cc-bg:#1f2937;--cc-fg:#f9fafb;--cc-border:#374151;--cc-muted:#9ca3af}}
</style>
<div id="cc-banner" role="dialog" aria-labelledby="cc-title" aria-describedby="cc-msg">
<h2 id="cc-title" class="cc-title">We use cookies</h2>
<p id="cc-msg" class="cc-msg">This site uses cookies to keep you signed in, analyze traffic, and improve content. Read our <a href="/privacy-policy" target="_blank" rel="noopener">Privacy Policy</a></p>
<div class="cc-cats">
<div class="cc-cat">
<label class="cc-cat-label">
<input type="checkbox" data-cat="essential" checked disabled />
<span class="cc-cat-title">Essential</span>
<span class="cc-cat-required">Always on</span>
</label>
<p class="cc-cat-desc">Required for the site to function (session, security, language preference). Cannot be disabled.</p>
</div>
<div class="cc-cat">
<label class="cc-cat-label">
<input type="checkbox" data-cat="analytics" />
<span class="cc-cat-title">Analytics</span>
</label>
<p class="cc-cat-desc">Help us understand how visitors use the site (page views, referrer, anonymized device info).</p>
</div>
<div class="cc-cat">
<label class="cc-cat-label">
<input type="checkbox" data-cat="advertising" />
<span class="cc-cat-title">Advertising</span>
</label>
<p class="cc-cat-desc">Used to show you relevant ads on this site and other sites you visit.</p>
</div>
<div class="cc-cat">
<label class="cc-cat-label">
<input type="checkbox" data-cat="functional" />
<span class="cc-cat-title">Functional</span>
</label>
<p class="cc-cat-desc">Remember your preferences (theme, layout, recent items) for a better experience.</p>
</div>
<div style="margin-top:12px;text-align:right">
<button class="cc-btn cc-btn-primary" data-cc="save">Save preferences</button>
</div>
</div>
<div class="cc-actions">
<button class="cc-btn cc-btn-secondary" data-cc="reject">Reject all</button>
<button class="cc-btn cc-btn-secondary" data-cc="customize">Customize</button>
<button class="cc-btn cc-btn-primary" data-cc="accept">Accept all</button>
</div>
</div>
<script>
(function(){
var COOKIE_NAME = "cc_consent";
var COOKIE_DAYS = 365;
var DEFAULTS = {"essential":true,"analytics":false,"advertising":false,"functional":false};
var listeners = [];
function readCookie(){
var m = document.cookie.match(new RegExp("(^|; )" + COOKIE_NAME + "=([^;]+)"));
if(!m) return null;
try { return JSON.parse(decodeURIComponent(m[2])); } catch(e){ return null; }
}
function writeCookie(val){
var d = new Date(); d.setTime(d.getTime() + COOKIE_DAYS*86400000);
document.cookie = COOKIE_NAME + "=" + encodeURIComponent(JSON.stringify(val)) + ";expires=" + d.toUTCString() + ";path=/;SameSite=Lax";
}
function notify(state){ listeners.forEach(function(fn){ try { fn(state); } catch(e){} }); }
var banner = document.getElementById("cc-banner");
function show(){ banner.classList.add("cc-show"); }
function hide(){ banner.classList.remove("cc-show"); banner.classList.remove("cc-expanded"); }
function applyToBanner(state){
banner.querySelectorAll('input[data-cat]').forEach(function(cb){
var k = cb.getAttribute('data-cat'); if(state[k] !== undefined) cb.checked = !!state[k];
});
}
function readBanner(){
var out = Object.assign({}, DEFAULTS);
banner.querySelectorAll('input[data-cat]').forEach(function(cb){ out[cb.getAttribute('data-cat')] = cb.checked; });
return out;
}
window.cookieConsent = {
get: function(){ return readCookie() || DEFAULTS; },
update: function(partial){
var next = Object.assign({}, readCookie() || DEFAULTS, partial);
writeCookie(next); applyToBanner(next); notify(next);
},
onChange: function(fn){ listeners.push(fn); },
show: function(){ show(); }
};
banner.addEventListener('click', function(e){
var act = e.target.getAttribute('data-cc'); if(!act) return;
if(act === 'accept'){
var s = {}; Object.keys(DEFAULTS).forEach(function(k){ s[k] = true; });
writeCookie(s); applyToBanner(s); notify(s); hide();
} else if(act === 'reject'){
var s = {}; Object.keys(DEFAULTS).forEach(function(k){ s[k] = DEFAULTS[k] === true; }); // essentials only
writeCookie(s); applyToBanner(s); notify(s); hide();
} else if(act === 'customize'){
banner.classList.add('cc-expanded');
} else if(act === 'save'){
var s = readBanner(); writeCookie(s); notify(s); hide();
}
});
if(!readCookie()) show();
})();
</script>Pick from 8 popular open-source licenses (MIT, Apache 2.0, GPL-3, BSD, MPL 2.0, ISC, Unlicense). See permissions / conditions / limitations side-by-side, then download a ready-to-commit LICENSE file.
Build a privacy policy in Markdown for Generic / GDPR (EU) / CCPA (California) / Vietnam Decree 13 from a short form.
Prepend a license header to source code in JS/TS/Python/Go/Rust/Java/C/CSS/HTML — SPDX, short, or full text.
GDPR and most modern privacy regulations require explicit opt-in before non-essential cookies are loaded — analytics, advertising, retargeting, personalization. The Cookie Banner Generator produces a single HTML snippet (with inline CSS and inline vanilla JS, no jQuery, no React, no CDN) you paste into the bottom of your <body> tag. The script exposes a small global API — window.cookieConsent.get(), .update(), .onChange(), .show() — that your existing analytics or ad code can check before firing. Users see a configurable banner (bottom bar, top bar, or floating card on either corner) with three buttons: Accept all, Reject all, and Customize, plus a per-category checkbox panel that opens on Customize. Preferences are persisted in a single first-party cookie (cc_consent) valid for 365 days; the banner stays hidden on subsequent visits unless the user explicitly opens the preference center via cookieConsent.show(). Per user agreement on the scope: this v1 emits opt-in flags only, NOT a TCF v2.2 consent string — adequate for first-party analytics or simple ad-network integrations, but if you need IAB Europe TCF v2.2 compliance for programmatic ad partners, you'll want a dedicated CMP.
cookieConsent.get() returns the current consent state: { essential: true, analytics: bool, advertising: bool, functional: bool }. cookieConsent.update({ analytics: true }) updates a subset and triggers all onChange listeners. cookieConsent.onChange(fn) registers a callback called whenever consent changes — the typical pattern is: cookieConsent.onChange(s => { if (s.analytics) loadGA(); }). cookieConsent.show() re-opens the banner for the user to revise preferences — bind this to a "Cookie settings" link in your footer.