- No data uploaded
- Instant results
Bootstrap 5 Cookie Consent Banner for Vanilla JS
A production-ready minimal cookie consent banner: a GDPR-style cookie banner with equally prominent “Accept all” and “Reject all”, granular analytics/marketing toggles (off by default), stored consent and Google Consent Mode v2 signals. Pick your brand color, toggle dark mode and corner radius — the live preview and the Vanilla JS code update instantly in your browser.
Live component editor
<!-- Requires Bootstrap 5.3 CSS + theme.css:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="theme.css"> -->
<div>
<div data-show="banner">
<div class="position-fixed start-0 end-0 bottom-0 z-3 p-3" role="region" aria-label="Cookie consent">
<div class="mw-4xl mx-auto p-4 bg-body border shadow-sm rounded-3">
<h2 class="fs-5 fw-semibold text-body-emphasis">We value your privacy</h2>
<p class="mt-2 small text-body-secondary">We use essential cookies to run this site. With your permission we also use analytics and marketing cookies. You can change your choice at any time. <a href="#" class="text-brand fw-medium text-decoration-none">Cookie policy</a></p>
<div data-show="prefs" hidden>
<div class="mt-3 d-flex flex-column gap-2">
<label class="d-flex align-items-start gap-2 small text-body">
<input type="checkbox" class="form-check-input mt-0 mt-1" checked disabled>
<span><strong>Strictly necessary</strong> — security, load balancing, your consent choice. Always on.</span>
</label>
<label class="d-flex align-items-start gap-2 small text-body">
<input id="cc-analytics" type="checkbox" class="form-check-input mt-0 mt-1">
<span><strong>Analytics</strong> — anonymous usage statistics that help us improve.</span>
</label>
<label class="d-flex align-items-start gap-2 small text-body">
<input id="cc-marketing" type="checkbox" class="form-check-input mt-0 mt-1">
<span><strong>Marketing</strong> — personalised ads and campaign measurement.</span>
</label>
</div>
</div>
<div class="mt-3 d-flex flex-wrap align-items-center gap-2">
<button type="button" class="btn btn-brand fw-semibold rounded-2" data-call="rejectAll" data-close="banner">Reject all</button>
<button type="button" class="btn btn-brand fw-semibold rounded-2" data-call="acceptAll" data-close="banner">Accept all</button>
<button type="button" class="btn btn-link link-body-emphasis text-decoration-none fw-semibold rounded-2" data-toggle="prefs" data-expanded="prefs" aria-expanded="false">Customize</button>
<div data-show="prefs" hidden>
<button type="button" class="btn btn-outline-secondary fw-semibold rounded-2" data-call="savePreferences" data-close="banner">Save preferences</button>
</div>
</div>
</div>
</div>
</div>
<div data-hide="banner" hidden>
<button type="button" class="position-fixed bottom-0 start-0 m-3 btn btn-outline-secondary fw-semibold rounded-2" data-open="banner">Cookie settings</button>
</div>
</div>
<script>
(() => {
const CONSENT_KEY = 'cookie-consent-v1'; // bump the version to re-ask after policy changes
function readConsent() {
try {
return JSON.parse(localStorage.getItem(CONSENT_KEY));
} catch {
return null; // storage blocked / SSR → treat as "not decided"
}
}
function saveConsent(choice) {
const record = { necessary: true, ...choice, timestamp: new Date().toISOString() };
try {
localStorage.setItem(CONSENT_KEY, JSON.stringify(record));
} catch {}
// Google Consent Mode v2: only grant what the user chose.
window.gtag?.('consent', 'update', {
analytics_storage: choice.analytics ? 'granted' : 'denied',
ad_storage: choice.marketing ? 'granted' : 'denied',
ad_user_data: choice.marketing ? 'granted' : 'denied',
ad_personalization: choice.marketing ? 'granted' : 'denied'
});
// Load optional scripts by listening for this event.
document.dispatchEvent(new CustomEvent('cookie-consent', { detail: record }));
}
function acceptAll() {
saveConsent({ analytics: true, marketing: true });
}
function rejectAll() {
saveConsent({ analytics: false, marketing: false });
}
function savePreferences() {
saveConsent({
analytics: Boolean(document.getElementById('cc-analytics')?.checked),
marketing: Boolean(document.getElementById('cc-marketing')?.checked)
});
}
const state = { banner: !readConsent(), prefs: false };
const actions = { acceptAll, rejectAll, savePreferences };
const pair = (s) => s.split(':');
const sync = () => {
document.querySelectorAll('[data-show]').forEach((el) => (el.hidden = !state[el.dataset.show]));
document.querySelectorAll('[data-hide]').forEach((el) => (el.hidden = !!state[el.dataset.hide]));
document.querySelectorAll('[data-when]').forEach((el) => { const [k, v] = pair(el.dataset.when); el.hidden = state[k] !== v; });
document.querySelectorAll('[data-pressed]').forEach((el) => { const [k, v] = pair(el.dataset.pressed); el.setAttribute('aria-pressed', String(state[k] === v)); });
document.querySelectorAll('[data-expanded]').forEach((el) => el.setAttribute('aria-expanded', String(!!state[el.dataset.expanded])));
};
document.addEventListener('click', (e) => {
const t = e.target.closest('[data-toggle],[data-open],[data-close],[data-set],[data-call]');
if (!t) return;
const { toggle, open, close, set, call } = t.dataset;
if (call) actions[call]?.(e);
if (toggle) state[toggle] = !state[toggle];
if (open) state[open] = true;
if (close) state[close] = false;
if (set) { const [k, v] = pair(set); state[k] = v; }
sync();
});
sync();
})();
</script>
/* theme.css — brand color #4f46e5 for Bootstrap 5.3 */
:root { --brand: #4f46e5; --brand-hover: #433cc3; --brand-rgb: 79, 70, 229; --brand-contrast: #ffffff; }
.btn-brand {
--bs-btn-color: var(--brand-contrast); --bs-btn-bg: var(--brand); --bs-btn-border-color: var(--brand);
--bs-btn-hover-color: var(--brand-contrast); --bs-btn-hover-bg: var(--brand-hover); --bs-btn-hover-border-color: var(--brand-hover);
--bs-btn-active-color: var(--brand-contrast); --bs-btn-active-bg: var(--brand-hover); --bs-btn-active-border-color: var(--brand-hover);
--bs-btn-focus-shadow-rgb: var(--brand-rgb);
}
.text-brand { color: var(--brand) !important; }
.border-brand { border: 2px solid var(--brand) !important; }
.bg-brand-subtle { background-color: rgba(var(--brand-rgb), .12) !important; }
.form-control:focus, .form-check-input:focus { border-color: var(--brand); box-shadow: 0 0 0 .25rem rgba(var(--brand-rgb), .25); }
.form-check-input:checked { background-color: var(--brand); border-color: var(--brand); }
.overlay { background: rgba(15, 23, 42, .5); backdrop-filter: blur(4px); z-index: 1050; }
.choice[aria-pressed="true"] { border-color: var(--brand) !important; color: var(--brand) !important; box-shadow: 0 0 0 .2rem rgba(var(--brand-rgb), .2); }
.mw-sm { max-width: 24rem; } .mw-md { max-width: 28rem; } .mw-lg { max-width: 32rem; } .mw-xl { max-width: 36rem; }
.mw-2xl { max-width: 42rem; } .mw-4xl { max-width: 56rem; } .mw-6xl { max-width: 72rem; }
More Bootstrap components for Vanilla JS
All components →- Footer A multi-column site footer with a brand description, three link groups, a newsletter sign-up field and a copyright row
- Login Modal A centered sign-in dialog with email and password fields, “remember me”, a forgot-password link and a social sign-in button
- Signup Form A registration card with name, email and password fields, a password hint, a terms-of-service checkbox and a primary call to action
- Navbar A responsive top navigation bar with a logo, primary links, a sign-in/CTA pair on desktop and a collapsible hamburger menu on mobile
- Contact Form A contact card with name, email, a topic selector, a message textarea and a submit button
- Pricing Table A three-tier pricing section with a monthly/yearly billing toggle, a highlighted “most popular” plan and feature checklists
- Hero Section A landing-page hero with an announcement badge, a large headline, supporting copy, primary and secondary calls to action and a row of social-proof stats
- Feature Grid A marketing section with a centered heading and a responsive grid of six feature cards, each with an icon badge, title and description
Install Bootstrap 5 for Vanilla JS
Include Bootstrap 5.3 from the CDN (or npm install bootstrap) and the generated theme.css:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="theme.css">Use the cookie consent banner in Vanilla JS
Paste the markup where the component should appear. The small inline <script> wires up the interactive state with one delegated click listener — no framework needed.
Making the banner actually compliant
- Block before consent: load analytics/marketing tags only after the
cookie-consentevent reports the matching category (or set Google Consent Mode v2 defaults todeniedbefore tags load). - Equal choice: keep “Reject all” on the first layer with the same visual weight as “Accept all”.
- Withdrawal: keep the “Cookie settings” button (or a footer link) available on every page.
- Record: the stored choice includes a timestamp; bump
CONSENT_KEYwhen your purposes change to re-ask.
This is an implementation pattern, not legal advice — have your privacy counsel review the wording and categories for your jurisdictions.
Accessibility & customization
The banner is a labelled region (role="region" with aria-label) rather than a blocking modal, preference toggles are native checkboxes with labels, and the always-available “Cookie settings” button lets keyboard users reopen it.
Every color, radius and dark-mode choice is baked into plain Bootstrap classes plus a tiny CSS-variable theme file, so the copied code has no runtime dependency on this site.
Compatibility
| Item | Details |
|---|---|
| CSS framework | Bootstrap 5.3+ (CSS only) |
| Library | Plain HTML + optional vanilla JS |
| Interactive state | banner, prefs |
| Dark mode | Yes — toggle in the editor |
| Style | Minimal |