Free Age Verification Gate Widget
A full-screen gate that verifies a visitor's age before they enter your site.
Age Verification Gate adds a full-screen overlay that asks visitors for their date of birth and only lets them through if they meet your minimum age. It is built for age-restricted sites like breweries, vape shops, and cannabis or alcohol retailers. Paste one script tag and verified visitors are remembered on return visits.
Embed this widget
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.
<script src="https://hayneslab.dev/widgets/age-verification-gate.js" data-hl-message="You must be 21 or older to enter. Please confirm your date of birth." async></script>
<!-- Free Age Verification Gate widget by haynes lab — https://hayneslab.dev/tools/widgets/age-verification-gate --><!-- Free Age Verification Gate widget by haynes lab — https://hayneslab.dev/tools/widgets/age-verification-gate -->
<script data-hl-message="You must be 21 or older to enter. Please confirm your date of birth.">
/*!
* haynes lab — Age Verification Gate widget (free)
* Docs & embed code: https://hayneslab.dev/tools/widgets/age-verification-gate
*/
(function () {
'use strict';
var PREFIX = 'hlw-age-verification-gate';
var STYLE_ID = 'hlw-age-verification-gate-styles';
var STORAGE_KEY = 'hlw-age-verification-gate-verified';
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
function findScript() {
return document.currentScript ||
document.querySelector('script[src*="widgets/age-verification-gate.js"]');
}
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
var css =
'.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
'.' + PREFIX + ' { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 2147483000;' +
' display: flex; align-items: center; justify-content: center; padding: 24px;' +
' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 16px; line-height: 1.5; color: #1f2937; }' +
'.' + PREFIX + '--stage { position: absolute; z-index: 10; }' +
'.' + PREFIX + ' [hidden] { display: none !important; }' +
'.' + PREFIX + '__panel { position: relative; width: 100%; max-width: 420px;' +
' padding: 32px 28px 24px; text-align: center; background: #ffffff; border-radius: 16px;' +
' box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); }' +
'.' + PREFIX + '__title { margin: 0 0 8px; font-size: 22px; font-weight: 700; color: #111827; }' +
'.' + PREFIX + '__message { margin: 0 0 20px; font-size: 15px; color: #4b5563; }' +
'.' + PREFIX + '__fields { display: flex; gap: 8px; }' +
'.' + PREFIX + '__select { flex: 1; min-width: 0; padding: 10px 8px; font-family: inherit;' +
' font-size: 14px; color: #1f2937; background: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; }' +
'.' + PREFIX + '__select:focus-visible { outline: 2px solid #111827; outline-offset: 1px; }' +
'.' + PREFIX + '__error { margin: 10px 0 0; font-size: 13px; color: #dc2626; }' +
'.' + PREFIX + '__button { display: block; width: 100%; margin-top: 16px; padding: 12px 16px;' +
' font-family: inherit; font-size: 15px; font-weight: 600; color: #ffffff; background: #111827;' +
' border: 0; border-radius: 8px; cursor: pointer; }' +
'.' + PREFIX + '__button:hover { background: #1f2937; }' +
'.' + PREFIX + '__button:focus-visible { outline: 2px solid #111827; outline-offset: 2px; }' +
'.' + PREFIX + '__button--ghost { margin-top: 8px; padding: 8px 16px; color: #6b7280;' +
' background: transparent; }' +
'.' + PREFIX + '__button--ghost:hover { color: #374151; background: #f3f4f6; }' +
'.' + PREFIX + '__close { position: absolute; top: 10px; right: 10px; width: 32px; height: 32px;' +
' padding: 0; font-size: 18px; line-height: 1; color: #6b7280; background: #f3f4f6;' +
' border: 0; border-radius: 50%; cursor: pointer; }' +
'.' + PREFIX + '__close:hover { color: #111827; background: #e5e7eb; }' +
'.' + PREFIX + '__credit { display: inline-flex; align-items: center; gap: 4px; margin-top: 18px;' +
' font-size: 11px; color: #9ca3af; text-decoration: none; }' +
'.' + PREFIX + '__credit svg { display: block; }' +
'.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
'.' + PREFIX + '-trigger { display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px;' +
' box-sizing: border-box; font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
' font-size: 14px; font-weight: 600; color: #ffffff; background: #111827; border: 0;' +
' border-radius: 8px; cursor: pointer; }' +
'.' + PREFIX + '-trigger:hover { background: #1f2937; }' +
'.' + PREFIX + '-trigger:focus-visible { outline: 2px solid #111827; outline-offset: 2px; }' +
'@media (max-width: 480px) { .' + PREFIX + ' { padding: 16px; }' +
' .' + PREFIX + '__panel { padding: 24px 20px 20px; } }';
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 != null) 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 calcAge(year, month, day) {
var now = new Date();
var age = now.getFullYear() - year;
var monthDiff = now.getMonth() - (month - 1);
if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < day)) age--;
return age;
}
function build(script) {
var minAge = parseInt(script.getAttribute('data-hl-min-age'), 10);
if (isNaN(minAge) || minAge < 1) minAge = 21;
var title = script.getAttribute('data-hl-title') || 'Age Verification Required';
var message = script.getAttribute('data-hl-message') ||
'Please confirm your date of birth to enter this site.';
var bg = script.getAttribute('data-hl-bg') || 'rgba(15, 10, 10, 0.92)';
var stage = script.closest ? script.closest('.widget-demo__stage') : null;
var inDemo = !!stage;
injectStyles();
function isVerified() {
try {
var stored = parseInt(window.localStorage.getItem(STORAGE_KEY), 10);
return !isNaN(stored) && stored >= minAge;
} catch (e) {
return false;
}
}
function markVerified() {
try {
window.localStorage.setItem(STORAGE_KEY, String(minAge));
} catch (e) { /* storage unavailable — gate will reappear next visit */ }
}
function buildSelect(label, placeholder) {
var select = el('select', PREFIX + '__select');
select.setAttribute('aria-label', label);
var first = el('option', null, placeholder);
first.value = '';
select.appendChild(first);
return select;
}
function makeCredit() {
var credit = el('a', PREFIX + '__credit');
credit.appendChild(logoSvg());
credit.appendChild(document.createTextNode('Powered by haynes lab'));
credit.href = 'https://hayneslab.dev/tools/widgets/age-verification-gate';
credit.target = '_blank';
credit.rel = 'noopener';
return credit;
}
function showGate() {
var previousOverflow = '';
var overlay = el('div', PREFIX + (inDemo ? ' ' + PREFIX + '--stage' : ''));
overlay.style.background = bg;
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-label', title);
var panel = el('div', PREFIX + '__panel');
overlay.appendChild(panel);
if (inDemo) {
var close = el('button', PREFIX + '__close', '×');
close.type = 'button';
close.setAttribute('aria-label', 'Close preview');
close.addEventListener('click', removeGate);
panel.appendChild(close);
}
panel.appendChild(el('h2', PREFIX + '__title', title));
panel.appendChild(el('p', PREFIX + '__message', message));
var form = el('div', PREFIX + '__form');
panel.appendChild(form);
var fields = el('div', PREFIX + '__fields');
var monthSelect = buildSelect('Birth month', 'Month');
MONTHS.forEach(function (name, i) {
var opt = el('option', null, name);
opt.value = String(i + 1);
monthSelect.appendChild(opt);
});
var daySelect = buildSelect('Birth day', 'Day');
for (var d = 1; d <= 31; d++) {
var dayOpt = el('option', null, String(d));
dayOpt.value = String(d);
daySelect.appendChild(dayOpt);
}
var yearSelect = buildSelect('Birth year', 'Year');
var thisYear = new Date().getFullYear();
for (var y = thisYear; y >= thisYear - 120; y--) {
var yearOpt = el('option', null, String(y));
yearOpt.value = String(y);
yearSelect.appendChild(yearOpt);
}
fields.appendChild(monthSelect);
fields.appendChild(daySelect);
fields.appendChild(yearSelect);
form.appendChild(fields);
var error = el('p', PREFIX + '__error');
error.setAttribute('role', 'alert');
error.hidden = true;
form.appendChild(error);
var verify = el('button', PREFIX + '__button', 'Verify my age');
verify.type = 'button';
form.appendChild(verify);
var deny = el('div', PREFIX + '__deny');
deny.hidden = true;
deny.appendChild(el('p', PREFIX + '__message',
'Sorry, you must be at least ' + minAge + ' years old to access this site.'));
var back = el('button', PREFIX + '__button ' + PREFIX + '__button--ghost',
inDemo ? 'Back to preview' : 'Go back');
back.type = 'button';
deny.appendChild(back);
panel.appendChild(deny);
panel.appendChild(makeCredit());
function removeGate() {
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
if (!inDemo) document.body.style.overflow = previousOverflow;
}
function fail(text) {
error.textContent = text;
error.hidden = false;
}
verify.addEventListener('click', function () {
error.hidden = true;
var m = parseInt(monthSelect.value, 10);
var d = parseInt(daySelect.value, 10);
var y = parseInt(yearSelect.value, 10);
if (!m || !d || !y) {
fail('Please select your full date of birth.');
return;
}
var dob = new Date(y, m - 1, d);
if (dob.getFullYear() !== y || dob.getMonth() !== m - 1 || dob.getDate() !== d) {
fail('That date does not look right. Please check it and try again.');
return;
}
if (calcAge(y, m, d) >= minAge) {
if (!inDemo) markVerified();
removeGate();
} else {
form.hidden = true;
deny.hidden = false;
back.focus();
}
});
back.addEventListener('click', function () {
if (inDemo) {
deny.hidden = true;
form.hidden = false;
} else {
window.history.back();
}
});
if (inDemo) {
stage.appendChild(overlay);
} else {
previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
document.body.appendChild(overlay);
monthSelect.focus();
}
}
if (inDemo) {
// Demo stage: never auto-overlay the landing page — render a preview
// trigger instead, with the credit link visible like every other widget.
var trigger = el('button', PREFIX + '-trigger', 'Preview age gate');
trigger.type = 'button';
trigger.addEventListener('click', showGate);
if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
script.insertAdjacentElement('afterend', trigger);
} else {
stage.appendChild(trigger);
}
trigger.insertAdjacentElement('afterend', makeCredit());
} else if (!isVerified()) {
showGate();
}
}
function init() {
var script = findScript();
if (!script) {
console.warn('[haynes lab age-verification-gate] Could not locate its own script tag. Nothing rendered.');
return;
}
build(script);
}
if (document.body) {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>Configuration
Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.
| Attribute | Default | Description |
|---|---|---|
data-hl-min-age | "21" | Minimum age in years a visitor must meet to pass the gate. |
data-hl-title | "Age Verification Required" | Heading shown at the top of the gate dialog. |
data-hl-message | "Please confirm your date of birth to enter this site." | Instruction text shown below the title, above the date-of-birth fields. |
data-hl-bg | "rgba(15, 10, 10, 0.92)" | CSS background for the full-screen overlay behind the gate dialog. |
Frequently asked questions
How do I install the age verification gate?
Paste the script tag anywhere in your page body — the gate renders as a full-screen overlay on top of your content as soon as the page loads, so no placeholder element is needed.
Can I change the minimum age and wording?
Yes. Use data-hl-min-age for the required age (e.g. 18 or 21), data-hl-title and data-hl-message for the heading and instructions, and data-hl-bg for the overlay background color.
Do verified visitors have to confirm again on every visit?
No. Once a visitor passes the gate, a flag is stored in their browser's localStorage and the gate is skipped on future visits from the same browser.
Does it work on WordPress, Shopify, or Squarespace?
Yes — it is a single vanilla JS file with no dependencies. Paste the script tag into any platform that lets you add custom HTML or scripts.
More free widgets
Cookie Consent Banner
A lightweight consent banner that respects your visitors.
View toolMarketing & SocialAnnouncement Bar
A slim banner for promos, launches, and urgent notices.
View toolMarketing & SocialExit Intent Popup
Catch visitors with one last offer right before they leave your site.
View toolNeed a custom widget?
We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.
Get in Touch