36 CSS Modals24 / 36

CSS + JSMIT licensed

Age Verification Modal

An age verification gate modal for regulated products (alcohol, cannabis, adult content, tobacco, gambling) with a signature Clip-Path Circuit Iris Aperture entrance — the modal reveals through an expanding circular clip-path that opens like a camera iris, distinctive and thematic for a "gatekeeping" moment. Warm amber palette (bourbon/whiskey aesthetic — commercial default for age gates), Date of Birth trio inputs (MM / DD / YYYY), plus session/persistent checkbox.

Published Updated

Live Demo
Try it

The code

<div class="md-06">
  <button class="md-06__trigger" id="md-06-open">Enter shop</button>
  <div class="md-06__backdrop" id="md-06-dialog" role="dialog" aria-modal="true" aria-labelledby="md-06-title">
    <div class="md-06__modal">
      <div class="md-06__lock" aria-hidden="true">🔒</div>
      <h2 class="md-06__title" id="md-06-title">You must be 21 or older</h2>
      <p class="md-06__sub">This site sells products intended for adults 21+. Please verify your date of birth to continue.</p>
      <form class="md-06__form" onsubmit="event.preventDefault()">
        <div class="md-06__dob">
          <label class="md-06__field">
            <span>Month</span>
            <input type="text" name="bday-month" autocomplete="bday-month" placeholder="MM" inputmode="numeric" pattern="[0-9]{1,2}" maxlength="2" required />
          </label>
          <label class="md-06__field">
            <span>Day</span>
            <input type="text" name="bday-day" autocomplete="bday-day" placeholder="DD" inputmode="numeric" pattern="[0-9]{1,2}" maxlength="2" required />
          </label>
          <label class="md-06__field">
            <span>Year</span>
            <input type="text" name="bday-year" autocomplete="bday-year" placeholder="YYYY" inputmode="numeric" pattern="[0-9]{4}" maxlength="4" required />
          </label>
        </div>
        <label class="md-06__remember">
          <input type="checkbox" />
          <span>Remember me for 30 days on this device</span>
        </label>
        <button type="submit" class="md-06__submit md-06__close">Enter site →</button>
        <button type="button" class="md-06__leave md-06__close">I'm under 21</button>
      </form>
      <p class="md-06__legal">Alcohol is intended for adults 21+. Please drink responsibly. Site restricted per US federal law.</p>
    </div>
  </div>
</div>
.md-06 {
  --page-bg: #1a0f08;
  --card: #f5ead0;
  --card-warm: #efe0ba;
  --ink: #2a1810;
  --sub: #7a5b3e;
  --brand: #c8552a;
  --brand-dark: #9d3f1f;
  --line: rgba(42,24,16,.16);
  font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: radial-gradient(80% 100% at 50% 0%, rgba(200,85,42,.16) 0%, transparent 60%), var(--page-bg);
  color: var(--ink);
}

.md-06 *,
.md-06 *::before,
.md-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-06 ::selection {
  background: var(--brand);
  color: #fff;
}

.md-06__trigger {
  padding: 12px 26px;
  border: 1px solid var(--brand);
  border-radius: 8px;
  background: transparent;
  color: var(--brand);
  font-family: inherit;
  font-size: .92rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-06__trigger:hover {
  background: var(--brand);
  color: #fff;
}

.md-06__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(26,15,8,.85);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease,visibility 0s linear .25s;
}

.md-06__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .25s ease,visibility 0s linear 0s;
}

.md-06__modal {
  position: relative;
  width: min(400px,100%);
  padding: 36px 24px 28px;
  border-radius: 14px;
  background: linear-gradient(180deg,var(--card),var(--card-warm));
  border: 1px solid rgba(42,24,16,.08);
  box-shadow: 0 40px 60px -20px rgba(26,15,8,.6);
  text-align: center;
  /* Signature entrance: Clip-Path Circuit Iris Aperture — camera iris opening */
  opacity: 0;
  clip-path: circle(0% at 50% 50%);
  transition: opacity .3s ease .05s,clip-path .55s cubic-bezier(.65,0,.35,1) .05s;
}

.md-06__backdrop.is-open .md-06__modal {
  opacity: 1;
  clip-path: circle(150% at 50% 50%);
}

.md-06__lock {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: rgba(200,85,42,.16);
  color: var(--brand);
  font-size: 22px;
  margin-bottom: 14px;
}

.md-06__title {
  font-family: 'Playfair Display',Georgia,serif;
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin-bottom: 8px;
}

.md-06__sub {
  font-size: .85rem;
  color: var(--sub);
  line-height: 1.5;
  margin-bottom: 22px;
}

.md-06__form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.md-06__dob {
  display: grid;
  grid-template-columns: 1fr 1fr 1.4fr;
  gap: 8px;
  width: 100%;
}

.md-06__field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  text-align: left;
  min-width: 0;
}

.md-06__field span {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--sub);
}

.md-06__field input {
  width: 100%;
  min-width: 0;
  padding: 11px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font-family: inherit;
  font-size: .95rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  transition: border-color .18s ease;
}

.md-06__field input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(200,85,42,.15);
}

.md-06__remember {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: .78rem;
  color: var(--sub);
  cursor: pointer;
  margin-top: 2px;
}

.md-06__remember input {
  width: 14px;
  height: 14px;
  accent-color: var(--brand);
}

.md-06__submit {
  padding: 12px 22px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .95rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 18px -6px rgba(200,85,42,.5);
  transition: background .18s ease,transform .12s ease;
  margin-top: 6px;
}

.md-06__submit:hover {
  background: var(--brand-dark);
  transform: translateY(-1px);
}

.md-06__submit:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.md-06__leave {
  padding: 8px 20px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--sub);
  font-family: inherit;
  font-size: .8rem;
  font-weight: 500;
  cursor: pointer;
  transition: color .18s ease;
}

.md-06__leave:hover {
  color: var(--ink);
  text-decoration: underline;
}

.md-06__legal {
  font-size: .68rem;
  color: var(--sub);
  line-height: 1.5;
  padding-top: 16px;
  margin-top: 6px;
  border-top: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .md-06__backdrop,
    .md-06__modal {
    transition: none!important;
  }

  .md-06__modal {
    clip-path: none!important;
  }
}
(function(){
  var root = document.querySelector('.md-06');
  if (!root) return;
  var openBtn = root.querySelector('#md-06-open');
  var dialog = root.querySelector('#md-06-dialog');
  var closeBtns = root.querySelectorAll('.md-06__close');
  var lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var firstInput = dialog.querySelector('input[autocomplete=bday-month]');
    if (firstInput) firstInput.focus();
    document.addEventListener('keydown', onKey);
  }
  function close() {
    dialog.classList.remove('is-open');
    document.documentElement.style.overflow = '';
    document.removeEventListener('keydown', onKey);
    if (lastFocus) lastFocus.focus();
  }
  function onKey(e) { if (e.key === 'Escape') close(); }

  openBtn.addEventListener('click', open);
  closeBtns.forEach(function(b){ b.addEventListener('click', close); });
  dialog.addEventListener('click', function(e){ if (e.target === dialog) close(); });
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Modal from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Age Verification Modal
Source: https://codefronts.com/components/css-modals/age-verification-modal/

An age verification gate modal for regulated products (alcohol, cannabis, adult content, tobacco, gambling) with a signature Clip-Path Circuit Iris Aperture entrance — the modal reveals through an expanding circular clip-path that opens like a camera iris, distinctive and thematic for a "gatekeeping" moment. Warm amber palette (bourbon/whiskey aesthetic — commercial default for age gates), Date of Birth trio inputs (MM / DD / YYYY), plus session/persistent checkbox.
## HTML
```html
<div class="md-06">
  <button class="md-06__trigger" id="md-06-open">Enter shop</button>
  <div class="md-06__backdrop" id="md-06-dialog" role="dialog" aria-modal="true" aria-labelledby="md-06-title">
    <div class="md-06__modal">
      <div class="md-06__lock" aria-hidden="true">🔒</div>
      <h2 class="md-06__title" id="md-06-title">You must be 21 or older</h2>
      <p class="md-06__sub">This site sells products intended for adults 21+. Please verify your date of birth to continue.</p>
      <form class="md-06__form" onsubmit="event.preventDefault()">
        <div class="md-06__dob">
          <label class="md-06__field">
            <span>Month</span>
            <input type="text" name="bday-month" autocomplete="bday-month" placeholder="MM" inputmode="numeric" pattern="[0-9]{1,2}" maxlength="2" required />
          </label>
          <label class="md-06__field">
            <span>Day</span>
            <input type="text" name="bday-day" autocomplete="bday-day" placeholder="DD" inputmode="numeric" pattern="[0-9]{1,2}" maxlength="2" required />
          </label>
          <label class="md-06__field">
            <span>Year</span>
            <input type="text" name="bday-year" autocomplete="bday-year" placeholder="YYYY" inputmode="numeric" pattern="[0-9]{4}" maxlength="4" required />
          </label>
        </div>
        <label class="md-06__remember">
          <input type="checkbox" />
          <span>Remember me for 30 days on this device</span>
        </label>
        <button type="submit" class="md-06__submit md-06__close">Enter site →</button>
        <button type="button" class="md-06__leave md-06__close">I'm under 21</button>
      </form>
      <p class="md-06__legal">Alcohol is intended for adults 21+. Please drink responsibly. Site restricted per US federal law.</p>
    </div>
  </div>
</div>
```
## CSS
```css
.md-06 {
  --page-bg: #1a0f08;
  --card: #f5ead0;
  --card-warm: #efe0ba;
  --ink: #2a1810;
  --sub: #7a5b3e;
  --brand: #c8552a;
  --brand-dark: #9d3f1f;
  --line: rgba(42,24,16,.16);
  font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background: radial-gradient(80% 100% at 50% 0%, rgba(200,85,42,.16) 0%, transparent 60%), var(--page-bg);
  color: var(--ink);
}

.md-06 *,
.md-06 *::before,
.md-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.md-06 ::selection {
  background: var(--brand);
  color: #fff;
}

.md-06__trigger {
  padding: 12px 26px;
  border: 1px solid var(--brand);
  border-radius: 8px;
  background: transparent;
  color: var(--brand);
  font-family: inherit;
  font-size: .92rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.md-06__trigger:hover {
  background: var(--brand);
  color: #fff;
}

.md-06__backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(26,15,8,.85);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease,visibility 0s linear .25s;
}

.md-06__backdrop.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity .25s ease,visibility 0s linear 0s;
}

.md-06__modal {
  position: relative;
  width: min(400px,100%);
  padding: 36px 24px 28px;
  border-radius: 14px;
  background: linear-gradient(180deg,var(--card),var(--card-warm));
  border: 1px solid rgba(42,24,16,.08);
  box-shadow: 0 40px 60px -20px rgba(26,15,8,.6);
  text-align: center;
  /* Signature entrance: Clip-Path Circuit Iris Aperture — camera iris opening */
  opacity: 0;
  clip-path: circle(0% at 50% 50%);
  transition: opacity .3s ease .05s,clip-path .55s cubic-bezier(.65,0,.35,1) .05s;
}

.md-06__backdrop.is-open .md-06__modal {
  opacity: 1;
  clip-path: circle(150% at 50% 50%);
}

.md-06__lock {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: rgba(200,85,42,.16);
  color: var(--brand);
  font-size: 22px;
  margin-bottom: 14px;
}

.md-06__title {
  font-family: 'Playfair Display',Georgia,serif;
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin-bottom: 8px;
}

.md-06__sub {
  font-size: .85rem;
  color: var(--sub);
  line-height: 1.5;
  margin-bottom: 22px;
}

.md-06__form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.md-06__dob {
  display: grid;
  grid-template-columns: 1fr 1fr 1.4fr;
  gap: 8px;
  width: 100%;
}

.md-06__field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  text-align: left;
  min-width: 0;
}

.md-06__field span {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--sub);
}

.md-06__field input {
  width: 100%;
  min-width: 0;
  padding: 11px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--ink);
  font-family: inherit;
  font-size: .95rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  transition: border-color .18s ease;
}

.md-06__field input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(200,85,42,.15);
}

.md-06__remember {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: .78rem;
  color: var(--sub);
  cursor: pointer;
  margin-top: 2px;
}

.md-06__remember input {
  width: 14px;
  height: 14px;
  accent-color: var(--brand);
}

.md-06__submit {
  padding: 12px 22px;
  border: none;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-family: inherit;
  font-size: .95rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 8px 18px -6px rgba(200,85,42,.5);
  transition: background .18s ease,transform .12s ease;
  margin-top: 6px;
}

.md-06__submit:hover {
  background: var(--brand-dark);
  transform: translateY(-1px);
}

.md-06__submit:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
}

.md-06__leave {
  padding: 8px 20px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--sub);
  font-family: inherit;
  font-size: .8rem;
  font-weight: 500;
  cursor: pointer;
  transition: color .18s ease;
}

.md-06__leave:hover {
  color: var(--ink);
  text-decoration: underline;
}

.md-06__legal {
  font-size: .68rem;
  color: var(--sub);
  line-height: 1.5;
  padding-top: 16px;
  margin-top: 6px;
  border-top: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .md-06__backdrop,
    .md-06__modal {
    transition: none!important;
  }

  .md-06__modal {
    clip-path: none!important;
  }
}
```

## JavaScript
```js
(function(){
  var root = document.querySelector('.md-06');
  if (!root) return;
  var openBtn = root.querySelector('#md-06-open');
  var dialog = root.querySelector('#md-06-dialog');
  var closeBtns = root.querySelectorAll('.md-06__close');
  var lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.classList.add('is-open');
    document.documentElement.style.overflow = 'hidden';
    var firstInput = dialog.querySelector('input[autocomplete=bday-month]');
    if (firstInput) firstInput.focus();
    document.addEventListener('keydown', onKey);
  }
  function close() {
    dialog.classList.remove('is-open');
    document.documentElement.style.overflow = '';
    document.removeEventListener('keydown', onKey);
    if (lastFocus) lastFocus.focus();
  }
  function onKey(e) { if (e.key === 'Escape') close(); }

  openBtn.addEventListener('click', open);
  closeBtns.forEach(function(b){ b.addEventListener('click', close); });
  dialog.addEventListener('click', function(e){ if (e.target === dialog) close(); });
})();
```

How this works

The signature entrance is a Clip-Path Circuit Iris Aperture: modal starts with clip-path: circle(0% at 50% 50%) — meaning zero visible area, everything is clipped away. On open, animates to clip-path: circle(150% at 50% 50%) — the circle expands beyond the modal bounds, revealing content in a growing circular aperture like a camera iris opening. Duration 550ms via cubic-bezier(.65, 0, .35, 1) — accelerating-then-decelerating ease that mimics mechanical iris movement.

Why medium intensity for age verification: it's regulatory (alcohol/cannabis/adult content laws) but not personal-security like healthcare. The iris theme is thematically appropriate (gatekeeping, revealing what's inside) without being frivolous. Warm amber palette (#c8552a primary + cream #f5ead0 card) — bourbon/whiskey/tobacco commercial default because these industries have converged on "warm/sophisticated" as the trust signal for age gates.

Date of Birth uses three separate numeric inputs (MM / DD / YYYY) with proper autocomplete='bday-month/bday-day/bday-year' — this is the industry standard for age verification because it enables mobile autofill via the birth date stored in the OS. A single date input is inaccessible for many older browsers and requires more typing. "Remember me for 30 days" persistent checkbox reduces re-verification friction on return visits.

WCAG 2.1 dialog pattern: role='dialog' + aria-modal='true'. Focus starts on the MM input. Escape KEEPS THE GATE (redirects to a safe page) — age gates cannot be closed casually or the regulatory purpose is defeated. In this demo Escape closes for UX, but production age gates redirect on close.

Make it yours

  • Change the required age by editing the JavaScript age calculation. Alcohol US: 21, EU: 18. Cannabis US recreational: 21. Adult content: 18 US, 18 UK per Online Safety Act 2023.
  • Change palette for different vertical — cannabis green (#22c55e) + cream, tobacco charcoal (#1a1a1a) + gold, adult content deep red (#dc2626) + black.
  • Extend the persistent-remember checkbox duration by editing the localStorage TTL (30 days default — set by legal/compliance team).
  • Add region-specific messaging ("Alcohol is intended for adults 21+ per US federal law") for products sold across jurisdictions with different age requirements.
  • For jurisdictions requiring ID upload (some cannabis states require government ID scan), add a file-upload flow instead of DOB inputs.

Gotchas — read before shipping

  • Age verification MUST be persistent (localStorage / cookie) OR session-persistent (sessionStorage) — otherwise users see the gate on every page load which reads as broken. FTC and state alcohol boards accept 30-day localStorage as reasonable.
  • Escape key on a real age gate should NOT close the modal — it should redirect to a safe page (google.com or an under-21 safe page). Age gates without close prevention can be bypassed by pressing Escape then interacting with the underlying content.
  • "Remember me for 30 days" checkbox stores in localStorage with a timestamp — but browser clears localStorage in incognito/private mode. Always fall back to session gate if localStorage is unavailable.
  • For cannabis / recreational drugs: many US states (CA, CO, WA) require BOTH age verification AND state-of-residence verification. A single DOB check is insufficient.
  • For adult content in UK/EU: the UK Online Safety Act 2023 requires "highly effective age assurance" — self-declared DOB is NOT sufficient. Real production requires ID upload + facial verification or credit card check.
  • The iris clip-path animation is a memorable entrance — but do NOT use full-speed animation on age gates for older users (60+). Add a reduced-speed variant that triggers on @media (min-width: 400px) and (max-width: 900px) for tablets or check if window.matchMedia('(prefers-reduced-motion)') matches.
  • The DOB inputs use autocomplete='bday-month/bday-day/bday-year' — do NOT use a single type='date' input. Single date inputs are inaccessible on older browsers and require more typing.

Browser support

ChromeSafariFirefoxEdge
76+9+103+76+

Floor set by backdrop-filter as this demo is written. The core technique itself goes back further — Chrome 55+.

clip-path: circle() animation. Baseline 2018.

Techniques used in this demo

Search CodeFronts

Loading…