Privacy regulations such as GDPR, the UK GDPR, and CCPA require virtually every website that sets non-essential cookies to inform visitors and — in many jurisdictions — collect their consent before those cookies fire. If you are building or maintaining a static HTML site, you cannot rely on a WordPress plugin to handle this for you. You need to implement a cookie consent banner directly in HTML, CSS, and vanilla JavaScript. This guide walks through the complete process: the markup, the styling with Bootstrap 5, the consent logic, and the correct way to gate third-party scripts so they only load after a visitor accepts.
Key Takeaways
- A GDPR-compliant cookie banner must give users a genuine choice and must not set non-essential cookies before consent is given.
- You can build a fully functional cookie consent banner in plain HTML and CSS without any paid third-party service.
- Bootstrap 5 utility classes and the
offcanvasortoastcomponents give you a polished UI with minimal custom CSS. - Consent state should be stored in
localStorageso the banner does not reappear on every page load. - Third-party scripts (analytics, ads, chat widgets) must be loaded conditionally, only after the user has accepted the relevant cookie category.
- The same pattern works inside a premium template like the Canvas HTML Template without modifying its core files.
Why Static Sites Still Need Cookie Consent
A common misconception is that static sites are exempt from cookie law because they have no server-side database. In reality, the obligation is triggered by the cookies and trackers themselves, not by the server technology. A static HTML page that loads Google Analytics, a Facebook Pixel, YouTube embeds, or Hotjar is processing personal data through those third-party scripts. Under GDPR Article 6, that requires a lawful basis — and for most analytics tools, that basis is informed, freely given consent.
If you are also deploying your static site on a platform such as Netlify or GitHub Pages — both covered in our guide on how to host a Bootstrap HTML template for free — the hosting layer does not change your obligations one bit.

Writing the Banner Markup
Place the following snippet just before the closing </body> tag on every HTML page. It uses Bootstrap 5 utility classes so it integrates cleanly with any Bootstrap-based project and requires almost no custom CSS.
<!-- Cookie Consent Banner -->
<div id="cookieConsentBanner"
class="position-fixed bottom-0 start-0 end-0 p-3 bg-dark text-white shadow-lg"
style="z-index: 1080; display: none;"
role="region"
aria-label="Cookie consent">
<div class="container">
<div class="row align-items-center g-3">
<div class="col-lg-8">
<p class="mb-0 small">
We use cookies to analyse site traffic and improve your experience.
By clicking <strong>Accept All</strong>, you consent to our use of
analytics and marketing cookies. You may also
<button class="btn btn-link btn-sm text-white p-0 align-baseline"
id="cookieManageBtn">manage your preferences</button>.
Read our <a href="/privacy-policy.html" class="text-white">
Privacy Policy</a>.
</p>
</div>
<div class="col-lg-4 d-flex gap-2 justify-content-lg-end">
<button class="btn btn-outline-light btn-sm" id="cookieRejectBtn">
Reject Non-Essential
</button>
<button class="btn btn-primary btn-sm" id="cookieAcceptBtn">
Accept All
</button>
</div>
</div>
</div>
</div>The banner is hidden by default (display:none) and revealed by JavaScript only when no prior consent record exists. Using position-fixed with bottom-0 keeps it anchored without obscuring page content until the user acts.
Consent Logic in Vanilla JavaScript
Add the following script block after the banner markup, or in a dedicated cookie-consent.js file loaded just before </body>. It stores the user’s decision in localStorage under the key cookieConsent with one of three values: accepted, rejected, or pending.
<script>
(function () {
var STORAGE_KEY = 'cookieConsent';
var banner = document.getElementById('cookieConsentBanner');
var acceptBtn = document.getElementById('cookieAcceptBtn');
var rejectBtn = document.getElementById('cookieRejectBtn');
function getConsent() {
return localStorage.getItem(STORAGE_KEY);
}
function setConsent(value) {
localStorage.setItem(STORAGE_KEY, value);
}
function hideBanner() {
banner.style.display = 'none';
}
function loadAnalytics() {
// Replace UA-XXXXXXXX-X with your actual Measurement ID
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
}
// On page load: check existing consent
var currentConsent = getConsent();
if (currentConsent === 'accepted') {
loadAnalytics();
} else if (!currentConsent || currentConsent === 'pending') {
banner.style.display = 'block';
}
// Accept button
acceptBtn.addEventListener('click', function () {
setConsent('accepted');
hideBanner();
loadAnalytics();
});
// Reject button
rejectBtn.addEventListener('click', function () {
setConsent('rejected');
hideBanner();
});
})();
</script>The key principle here is script gating: the analytics snippet is never included in the initial HTML. It is only injected into the DOM dynamically after the user clicks Accept. This is what makes the implementation genuinely GDPR-compliant rather than merely cosmetic.

Adding a Preferences Modal for Granular Control
Regulators increasingly expect sites to offer category-level control — at minimum, a distinction between strictly necessary, analytics, and marketing cookies. Bootstrap 5’s modal component makes this straightforward. Trigger it from the “manage your preferences” button you already have in the banner.
<!-- Cookie Preferences Modal -->
<div class="modal fade" id="cookiePrefsModal" tabindex="-1"
aria-labelledby="cookiePrefsLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cookiePrefsLabel">Cookie Preferences</h5>
<button type="button" class="btn-close"
data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox"
id="cookieNecessary" checked disabled>
<label class="form-check-label" for="cookieNecessary">
<strong>Strictly Necessary</strong> — always active
</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="cookieAnalytics">
<label class="form-check-label" for="cookieAnalytics">
<strong>Analytics</strong> — helps us understand traffic
</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="cookieMarketing">
<label class="form-check-label" for="cookieMarketing">
<strong>Marketing</strong> — personalised ads and retargeting
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary"
id="cookieSavePrefsBtn">Save Preferences</button>
</div>
</div>
</div>
</div>Wire the “manage” button to open this modal using Bootstrap’s JavaScript API:
<script>
document.getElementById('cookieManageBtn').addEventListener('click', function () {
var modal = new bootstrap.Modal(
document.getElementById('cookiePrefsModal')
);
modal.show();
});
document.getElementById('cookieSavePrefsBtn').addEventListener('click', function () {
var analytics = document.getElementById('cookieAnalytics').checked;
var marketing = document.getElementById('cookieMarketing').checked;
localStorage.setItem('cookieAnalytics', analytics ? 'true' : 'false');
localStorage.setItem('cookieMarketing', marketing ? 'true' : 'false');
localStorage.setItem('cookieConsent', 'custom');
if (analytics) { loadAnalytics(); }
bootstrap.Modal.getInstance(
document.getElementById('cookiePrefsModal')
).hide();
document.getElementById('cookieConsentBanner').style.display = 'none';
});
</script>Integrating with the Canvas HTML Template
If you are building on the Canvas HTML Template, the banner and modal slots neatly into the existing layout without touching plugins.min.js or functions.bundle.js. Drop the banner <div> immediately before </body> on each page, or if you use a shared footer.html partial (common in build pipelines with tools like Panini or Eleventy), add it once to that partial.
Canvas uses the CSS custom property --cnvs-themecolor for its brand colour. You can tint the Accept button to match by replacing btn-primary with an inline style or a small utility override:
<button class="btn btn-sm"
style="background-color: var(--cnvs-themecolor); color: #fff; border: none;"
id="cookieAcceptBtn">Accept All</button>This keeps the consent UI visually consistent with the rest of the site without hardcoding a hex value. For broader colour and font customisation across a Canvas project, see our post on customising Canvas Template colours and fonts.
GDPR Compliance Checklist for Your Banner
Building the UI is only half the requirement. Before you ship, verify each of the following:
- No pre-ticked boxes. Analytics and marketing toggles must default to off.
- Equal prominence. “Reject” must be as easy to find and click as “Accept”. Do not hide the reject option in a sub-menu.
- No cookie walls. Access to the site’s content must not be conditional on accepting cookies (with very limited exceptions).
- Consent is re-requestable. Users must be able to withdraw consent later. Provide a “Cookie Settings” link in your footer that re-opens the preferences modal.
- Consent record. For higher-traffic sites, consider logging the timestamp and consent version server-side or via a compliant SaaS layer.
- No scripts firing before consent. Audit your
<head>to ensure no tracking pixels or analytics tags are hardcoded outside the gated loader function.
If your static site also includes a contact form that posts to a third-party endpoint, check the data-handling implications covered in our guide on integrating a contact form into a static HTML template.
Frequently Asked Questions
If your site sets no cookies at all — no analytics, no embeds, no session storage used for tracking — then strictly speaking you do not need a consent banner. Most static sites do load at least one third-party script that sets cookies, so audit your network requests carefully before assuming you are exempt.
localStorage is not a cookie, so storing a consent preference flag there does not itself require consent. It is an accepted practice for persisting the user’s choice. The important point is that no tracking cookies are set before that choice is made and stored.
Yes. Services such as Cookiebot, CookieYes, and Osano offer free tiers and handle the compliance complexity for you, including automatic cookie scanning. The trade-off is an additional third-party JavaScript dependency and, in some cases, a cookie set by the consent platform itself. The DIY approach in this article gives you full control with zero third-party dependencies.
Add a “Cookie Settings” link somewhere accessible — typically the site footer. Wire it to clear the relevant localStorage keys and reload the page, or simply call banner.style.display = 'block' and pre-populate the toggles from the stored values. Reloading is simpler and ensures any gated scripts are in a clean state.
If you later move to WordPress, replace the custom banner with a dedicated GDPR plugin such as Complianz or WPML Cookie Notice. The hand-rolled banner described here is designed for purely static deployments. Our post on converting a Bootstrap 5 HTML template to a WordPress theme covers the broader migration considerations.
Looking for a production-ready Bootstrap 5 HTML template? Browse Canvas Template demos and find the perfect starting point for your next project.
If you’re building with the Canvas HTML Template and want to ship production-ready Bootstrap 5 layouts faster, try Canvas Builder free — the visual builder that exports clean Canvas-ready markup in minutes.
Skip the setup — build it free
Spin up a complete Bootstrap 5 site, blog included, with Canvas Builder. No coding, no cost.
Canvas Team
Tutorials and tips for building beautiful Bootstrap 5 websites with the Canvas HTML Template and Canvas Builder.
More from the Canvas Blog