Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.
<!-- Free Cookie Consent Banner widget by haynes lab — https://hayneslab.dev/tools/widgets/cookie-consent -->
<script data-hl-message="We use cookies to understand how visitors use this site and to improve your experience." data-hl-policy-link="https://hayneslab.dev/privacy">
/*!
* haynes lab — Cookie Consent Banner widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/cookie-consent
*/
(function () {
'use strict';
var STORAGE_KEY = 'hlw_cookie-consent_choice';
var STYLE_ID = 'hlw-cookie-consent-styles';
var ROOT_CLASS = 'hlw-cookie-consent';
var script = document.currentScript;
if (!script) {
script = document.querySelector('script[src*="widgets/cookie-consent.js"]');
}
if (!script) {
console.warn('[hlw-cookie-consent] Could not locate its own <script> tag; banner not rendered.');
return;
}
function attr(name, fallback) {
var v = script.getAttribute('data-hl-' + name);
return v === null ? fallback : v;
}
var config = {
message: attr('message', 'This site uses cookies to improve your experience.'),
policyLink: attr('policy-link', ''),
acceptText: attr('accept-text', 'Accept'),
declineText: attr('decline-text', 'Decline'),
position: attr('position', 'bottom'),
bg: attr('bg', '#141010'),
accent: attr('accent', '#f97316')
};
function getChoice() {
try {
return window.localStorage.getItem(STORAGE_KEY);
} catch (e) {
return null; // storage unavailable (private mode etc.) — show the banner
}
}
function setChoice(choice) {
try {
window.localStorage.setItem(STORAGE_KEY, choice);
} catch (e) {
console.warn('[hlw-cookie-consent] Could not persist consent choice (storage unavailable).');
}
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + ROOT_CLASS + ', .' + ROOT_CLASS + ' * { box-sizing: border-box; margin: 0; padding: 0; }' +
'.' + ROOT_CLASS + ' { position: fixed; z-index: 2147483000; font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #f5f3f0; background: var(--hlw-cc-bg, #141010); box-shadow: 0 -2px 16px rgba(0,0,0,0.18); }' +
'.' + ROOT_CLASS + '--bottom { left: 0; right: 0; bottom: 0; }' +
'.' + ROOT_CLASS + '--bottom-left { left: 16px; bottom: 16px; max-width: 380px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.28); }' +
'.' + ROOT_CLASS + '__inner { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 14px 18px; }' +
'.' + ROOT_CLASS + '--bottom-left .' + ROOT_CLASS + '__inner { flex-direction: column; align-items: stretch; }' +
'.' + ROOT_CLASS + '__text { flex: 1 1 280px; }' +
'.' + ROOT_CLASS + '__link { color: inherit; text-decoration: underline; text-underline-offset: 2px; opacity: 0.85; }' +
'.' + ROOT_CLASS + '__link:hover { opacity: 1; }' +
'.' + ROOT_CLASS + '__actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }' +
'.' + ROOT_CLASS + '__btn { font: inherit; font-weight: 600; font-size: 13px; line-height: 1; padding: 10px 18px; border: 0; border-radius: 8px; cursor: pointer; white-space: nowrap; }' +
'.' + ROOT_CLASS + '__btn--accept { background: var(--hlw-cc-accent, #f97316); color: #ffffff; }' +
'.' + ROOT_CLASS + '__btn--accept:hover { filter: brightness(1.1); }' +
'.' + ROOT_CLASS + '__btn--decline { background: transparent; color: inherit; border: 1px solid rgba(255,255,255,0.35); }' +
'.' + ROOT_CLASS + '__btn--decline:hover { border-color: rgba(255,255,255,0.7); }' +
'.' + ROOT_CLASS + '__btn:focus-visible, .' + ROOT_CLASS + ' a:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; }' +
'.' + ROOT_CLASS + '__credit { font-size: 11px; opacity: 0.55; }' +
'.' + ROOT_CLASS + '--bottom .' + ROOT_CLASS + '__credit { position: absolute; right: 8px; bottom: 4px; }' +
'.' + ROOT_CLASS + '--bottom-left .' + ROOT_CLASS + '__credit { text-align: right; padding: 0 18px 8px; }' +
'.' + ROOT_CLASS + '__credit a { display: inline-flex; align-items: center; gap: 4px; color: inherit; text-decoration: none; }' +
'.' + ROOT_CLASS + '__credit a svg { display: block; }' +
'.' + ROOT_CLASS + '__credit a:hover { text-decoration: underline; }' +
'@media (max-width: 560px) { .' + ROOT_CLASS + '__inner { flex-direction: column; align-items: stretch; } .' + ROOT_CLASS + '__actions { justify-content: stretch; } .' + ROOT_CLASS + '__btn { flex: 1; } }';
var style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function logoSvg() {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 32 32');
svg.setAttribute('width', '13');
svg.setAttribute('height', '13');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
function circle(cx, cy, r, attrs) {
var c = document.createElementNS(svgNS, 'circle');
c.setAttribute('cx', cx);
c.setAttribute('cy', cy);
c.setAttribute('r', r);
for (var key in attrs) c.setAttribute(key, attrs[key]);
svg.appendChild(c);
}
circle(16, 16, 16, { fill: '#0f0a0a' });
circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
circle(16, 16, 3, { fill: '#06b6d4' });
circle(24.5, 7.5, 2, { fill: '#f97316' });
circle(7, 16, 1.8, { fill: '#f97316' });
circle(20, 20, 1.5, { fill: '#f97316' });
return svg;
}
function render() {
// The script may run again (live demo re-injection, double embed) —
// remove any previous instance so banners never stack.
var oldRoots = document.querySelectorAll('.' + ROOT_CLASS);
for (var i = 0; i < oldRoots.length; i++) {
oldRoots[i].parentNode.removeChild(oldRoots[i]);
}
if (getChoice()) return; // visitor already answered
injectStyles();
var floating = config.position === 'bottom-left';
var root = el('div', ROOT_CLASS + ' ' + ROOT_CLASS + (floating ? '--bottom-left' : '--bottom'));
root.setAttribute('role', 'region');
root.setAttribute('aria-label', 'Cookie consent notice');
root.style.setProperty('--hlw-cc-bg', config.bg);
root.style.setProperty('--hlw-cc-accent', config.accent);
var inner = el('div', ROOT_CLASS + '__inner');
var text = el('p', ROOT_CLASS + '__text');
text.appendChild(document.createTextNode(config.message + ' '));
if (config.policyLink) {
var link = el('a', ROOT_CLASS + '__link', 'Learn more');
link.href = config.policyLink;
link.target = '_blank';
link.rel = 'noopener';
text.appendChild(link);
}
inner.appendChild(text);
var actions = el('div', ROOT_CLASS + '__actions');
if (config.declineText !== '') {
var declineBtn = el('button', ROOT_CLASS + '__btn ' + ROOT_CLASS + '__btn--decline', config.declineText);
declineBtn.type = 'button';
declineBtn.setAttribute('aria-label', 'Decline cookies');
declineBtn.addEventListener('click', function () { dismiss('declined'); });
actions.appendChild(declineBtn);
}
var acceptBtn = el('button', ROOT_CLASS + '__btn ' + ROOT_CLASS + '__btn--accept', config.acceptText);
acceptBtn.type = 'button';
acceptBtn.setAttribute('aria-label', 'Accept cookies');
acceptBtn.addEventListener('click', function () { dismiss('accepted'); });
actions.appendChild(acceptBtn);
inner.appendChild(actions);
root.appendChild(inner);
var credit = el('div', ROOT_CLASS + '__credit');
var creditLink = el('a');
creditLink.appendChild(logoSvg());
creditLink.appendChild(document.createTextNode('Powered by haynes lab'));
creditLink.href = 'https://hayneslab.dev/tools/widgets/cookie-consent';
creditLink.target = '_blank';
creditLink.rel = 'noopener';
credit.appendChild(creditLink);
root.appendChild(credit);
function dismiss(choice) {
setChoice(choice);
if (root.parentNode) root.parentNode.removeChild(root);
}
document.body.appendChild(root);
}
if (document.body) {
render();
} else {
document.addEventListener('DOMContentLoaded', render);
}
})();
</script>