Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="mo-i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="mo-i-invite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9.5" cy="8" r="3.5"/><path d="M3.5 19.5c.7-3.4 3-5 6-5s5.3 1.6 6 5M18.5 8v6M15.5 11h6"/></symbol>
  <symbol id="mo-i-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 12h4l2.5-6.5 4 13 2.5-6.5h4"/></symbol>
  <symbol id="mo-i-cookie" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 12.5a8.5 8.5 0 1 1-9-9 3 3 0 0 0 3.5 3.5 3 3 0 0 0 3 3 2.5 2.5 0 0 0 2.5 2.5z"/><path d="M8.5 9.5h.01M9.5 15h.01M14.5 14.5h.01" stroke-width="2.6"/></symbol>
</svg>

<div><span class="lbl">Zoom and fade</span>
<div class="btns"><button class="mo-btn is-primary" type="button" commandfor="mo-invite" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-invite"/></svg>Invite people</button></div></div>

<div><span class="lbl">Slide-over panel</span>
<div class="btns"><button class="mo-btn" type="button" commandfor="mo-activity" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-pulse"/></svg>Activity</button></div></div>

<div><span class="lbl">Pop-up sheet</span>
<div class="btns"><button class="mo-btn" type="button" commandfor="mo-cookies" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-cookie"/></svg>Cookie settings</button></div></div>

<!-- 1. Zoom and fade. closedby="closerequest": Esc closes it, a stray click outside does not, so a half-typed address survives. -->
<dialog class="mo mo-zoom" id="mo-invite" closedby="closerequest" aria-labelledby="mo-invite-title">
  <div class="mo-head">
    <span class="mo-icon" aria-hidden="true"><svg><use href="#mo-i-invite"/></svg></span>
    <div class="mo-titles"><h2 class="mo-title" id="mo-invite-title">Invite people to Northfield</h2><p class="mo-sub">They get an email with a link to join the workspace.</p></div>
    <button class="mo-x" type="button" commandfor="mo-invite" command="close" aria-label="Close"><svg aria-hidden="true"><use href="#mo-i-x"/></svg></button>
  </div>
  <div class="mo-body">
    <form class="mo-invite" method="dialog">
      <div class="mo-field"><label for="mo-invite-email">Email address</label><input class="mo-input" id="mo-invite-email" name="email" type="email" placeholder="[email protected]" autocomplete="off" required autofocus></div>
      <div class="mo-field"><label for="mo-invite-role">Role</label><select class="mo-input" id="mo-invite-role" name="role"><option>Viewer</option><option selected>Editor</option><option>Admin</option></select></div>
      <button class="mo-btn is-primary" value="invite">Send invite</button>
    </form>
    <h3 class="mo-h3">People with access</h3>
    <ul class="mo-people">
      <li><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Hana Kobayashi"><div class="mo-person"><b>Hana Kobayashi</b><span>[email protected]</span></div><span class="mo-role is-owner">Owner</span></li>
      <li><img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Theo Marchetti"><div class="mo-person"><b>Theo Marchetti</b><span>[email protected]</span></div><span class="mo-role">Editor</span></li>
      <li><img src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Priya Raman"><div class="mo-person"><b>Priya Raman</b><span>[email protected]</span></div><span class="mo-role">Viewer</span></li>
    </ul>
  </div>
  <div class="mo-foot">
    <p class="mo-note">3 of 10 seats used</p>
    <button class="mo-btn" type="button" commandfor="mo-invite" command="close">Done</button>
  </div>
</dialog>

<!-- 2. Slide-over panel. closedby="any": light dismiss by a backdrop click or Esc, nothing to lose. -->
<dialog class="mo mo-panel" id="mo-activity" closedby="any" aria-labelledby="mo-activity-title">
  <div class="mo-head">
    <div class="mo-titles"><h2 class="mo-title" id="mo-activity-title">Activity</h2><p class="mo-sub">Northfield Ceramics · 3 unread</p></div>
    <button class="mo-x" type="button" commandfor="mo-activity" command="close" aria-label="Close"><svg aria-hidden="true"><use href="#mo-i-x"/></svg></button>
  </div>
  <div class="mo-feed" tabindex="0" role="region" aria-label="Activity feed">
    <h3 class="mo-h3">Today</h3>
    <ol class="mo-events">
      <li class="is-new"><span class="mo-av"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="mo-event"><p><span class="mo-sr">Unread: </span><b>Hana Kobayashi</b> published <strong>Autumn glaze catalogue</strong></p><time datetime="2026-09-25T09:42">9:42</time></div></li>
      <li class="is-new"><span class="mo-av"><img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="mo-event"><p><span class="mo-sr">Unread: </span><b>Theo Marchetti</b> commented on <strong>Kiln schedule, week 40</strong></p><p class="mo-quote">Firing moves to Thursday. The kiln is booked for the tile order on Wednesday.</p><time datetime="2026-09-25T09:15">9:15</time></div></li>
      <li class="is-new"><span class="mo-av"><img src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="mo-event"><p><span class="mo-sr">Unread: </span><b>Priya Raman</b> uploaded 12 photos to <strong>Stoneware mugs</strong></p><time datetime="2026-09-25T08:58">8:58</time></div></li>
      <li><span class="mo-av" aria-hidden="true">JW</span><div class="mo-event"><p><b>Jonas Weber</b> invited [email protected] as a Viewer</p><time datetime="2026-09-25T08:31">8:31</time></div></li>
      <li><span class="mo-av"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="mo-event"><p><b>Hana Kobayashi</b> changed the price of <strong>Speckled bowl, large</strong> to £34</p><time datetime="2026-09-25T08:03">8:03</time></div></li>
    </ol>
    <h3 class="mo-h3">Yesterday</h3>
    <ol class="mo-events">
      <li><span class="mo-av"><img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="mo-event"><p><b>Theo Marchetti</b> completed <strong>Order 1187: pack and ship</strong></p><time datetime="2026-09-24T17:40">17:40</time></div></li>
      <li><span class="mo-av" aria-hidden="true">AM</span><div class="mo-event"><p><b>Ama Mensah</b> joined the workspace as an Editor</p><time datetime="2026-09-24T16:12">16:12</time></div></li>
      <li><span class="mo-av"><img src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="mo-event"><p><b>Priya Raman</b> archived <strong>Summer market 2026</strong></p><time datetime="2026-09-24T11:05">11:05</time></div></li>
    </ol>
  </div>
  <form class="mo-foot" method="dialog">
    <a class="mo-btn" href="#">Full log</a>
    <button class="mo-btn is-primary" value="read">Mark all as read</button>
  </form>
</dialog>

<!-- 3. Pop-up sheet. closedby="closerequest": no light dismiss, because a click on the page behind is not a consent choice.
     Esc still closes it, and saves nothing. -->
<dialog class="mo mo-sheet" id="mo-cookies" closedby="closerequest" aria-labelledby="mo-cookies-title" aria-describedby="mo-cookies-desc">
  <form class="mo-form" method="dialog">
    <div class="mo-head">
      <span class="mo-icon" aria-hidden="true"><svg><use href="#mo-i-cookie"/></svg></span>
      <div class="mo-titles"><h2 class="mo-title" id="mo-cookies-title">Cookie settings</h2><p class="mo-sub" id="mo-cookies-desc">Choose which cookies Northfield Ceramics may use. You can change this at any time from the link in the footer.</p></div>
    </div>
    <div class="mo-body">
      <fieldset class="mo-choices">
        <legend class="mo-sr">Cookie categories</legend>
        <div class="mo-choice">
          <div class="mo-choice-text"><label for="mo-ck-necessary">Necessary <span class="mo-pill">Always on</span></label><p id="mo-ck-necessary-desc">Keep you signed in, remember your basket and protect the checkout.</p></div>
          <input class="mo-switch" type="checkbox" role="switch" id="mo-ck-necessary" aria-describedby="mo-ck-necessary-desc" checked disabled>
        </div>
        <div class="mo-choice">
          <div class="mo-choice-text"><label for="mo-ck-analytics">Analytics</label><p id="mo-ck-analytics-desc">Count visits and see which pages are read, so we can fix what is slow or confusing.</p></div>
          <input class="mo-switch" type="checkbox" role="switch" id="mo-ck-analytics" name="analytics" aria-describedby="mo-ck-analytics-desc">
        </div>
        <div class="mo-choice">
          <div class="mo-choice-text"><label for="mo-ck-marketing">Marketing</label><p id="mo-ck-marketing-desc">Show our ads on other sites and measure whether they lead to an order.</p></div>
          <input class="mo-switch" type="checkbox" role="switch" id="mo-ck-marketing" name="marketing" aria-describedby="mo-ck-marketing-desc">
        </div>
      </fieldset>
    </div>
    <div class="mo-foot">
      <button class="mo-btn" value="reject">Reject all</button>
      <button class="mo-btn is-primary" value="save">Save choices</button>
    </div>
  </form>
</dialog>
CSS
/* Modern CSS modals: the native <dialog> doing with HTML and CSS what usually needs JavaScript.
   - Opened by invoker commands: <button commandfor="id" command="show-modal">. Closed by command="close" or <form method="dialog">.
   - closedby="any" adds light dismiss (backdrop click + Esc); closedby="closerequest" allows Esc only.
   - Enter AND exit animations in CSS alone: @starting-style gives the entry state, and transition-behavior:allow-discrete
     on display and overlay keeps the dialog (and its ::backdrop) in the top layer until the exit transition ends.
   @starting-style is keyed to :modal, so a static copy with [open] (e.g. a preview) renders in its final state. */
html{scrollbar-gutter:stable}          /* the page does not shift sideways when the scroll lock hides its scrollbar */
html:has(.mo:modal){overflow:hidden}   /* scroll lock while one of these dialogs is open */

.mo-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.mo-btn{--mo-accent:#c2410c;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:42px;padding:9px 16px;border:1px solid #d6d3d1;border-radius:10px;
  background:#fff;color:#1c1917;font-family:inherit;font-size:.9rem;font-weight:600;line-height:1.2;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .15s,border-color .15s,box-shadow .15s}
.mo-btn:hover{background:#f5f5f4;border-color:#a8a29e}
.mo-btn:focus-visible{outline:2px solid var(--mo-accent);outline-offset:2px}
.mo-btn.is-primary{background:var(--mo-accent);border-color:var(--mo-accent);color:#fff;box-shadow:0 6px 16px -8px rgba(234,88,12,.8)}
.mo-btn.is-primary:hover{background:#9a3412;border-color:#9a3412}
.mo-btn svg{width:18px;height:18px;flex:none}

/* The dialog shell, shared by all three variants */
.mo{--mo-accent:#c2410c;--mo-ink:#1c1917;--mo-muted:#57534e;--mo-line:#e7e5e4;--mo-soft:#fafaf9;--mo-dur:.26s;--mo-ease:cubic-bezier(.2,.8,.2,1);
  box-sizing:border-box;flex-direction:column;max-width:none;padding:0;border:0;background:#fff;color:var(--mo-ink);font-size:.95rem;line-height:1.55;overflow:hidden;
  box-shadow:0 0 0 1px rgba(28,25,23,.06),0 30px 70px -22px rgba(28,25,23,.5);
  transition:opacity var(--mo-dur) var(--mo-ease),scale var(--mo-dur) var(--mo-ease),translate var(--mo-dur) var(--mo-ease),
    display var(--mo-dur) allow-discrete,overlay var(--mo-dur) allow-discrete}
.mo *,.mo *::before,.mo *::after{box-sizing:border-box}
.mo[open]{display:flex}
.mo::backdrop{background-color:rgb(28 25 23 / 0);backdrop-filter:blur(0);
  transition:background-color .26s,backdrop-filter .26s,display .26s allow-discrete,overlay .26s allow-discrete}
.mo[open]::backdrop{background-color:rgb(28 25 23 / .5);backdrop-filter:blur(3px)}
@starting-style{.mo:modal::backdrop{background-color:rgb(28 25 23 / 0);backdrop-filter:blur(0)}}

.mo-form{display:flex;flex-direction:column;flex:1;min-height:0;margin:0}
.mo-head{display:flex;align-items:flex-start;gap:14px;padding:22px 24px 6px}
.mo-titles{flex:1;min-width:0}
.mo-title{margin:0;font-size:1.15rem;font-weight:700;letter-spacing:-.01em;line-height:1.35}
.mo-sub{margin:3px 0 0;color:var(--mo-muted);font-size:.85rem}
.mo-icon{flex:none;display:grid;place-items:center;width:42px;height:42px;border-radius:12px;background:linear-gradient(135deg,#fff7ed,#ffedd5);color:var(--mo-accent)}
.mo-icon svg{width:22px;height:22px}
.mo-x{flex:none;display:grid;place-items:center;width:36px;height:36px;margin:-4px -8px 0 0;padding:0;border:0;border-radius:10px;background:transparent;color:var(--mo-muted);cursor:pointer;
  transition:background-color .15s,color .15s}
.mo-x:hover{background:#f5f5f4;color:var(--mo-ink)}
.mo-x:focus-visible{outline:2px solid var(--mo-accent);outline-offset:1px}
.mo-x svg{width:20px;height:20px}
.mo-body{padding:16px 24px 22px}
.mo-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:10px;margin:0;padding:14px 24px;border-top:1px solid var(--mo-line);background:var(--mo-soft)}
.mo-note{margin:0 auto 0 0;color:var(--mo-muted);font-size:.85rem}
.mo-h3{margin:0 0 6px;color:var(--mo-muted);font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}

/* 1. Zoom and fade: scales up from .94 and fades in, and does the reverse on close */
.mo-zoom{position:fixed;inset:0;width:min(500px,calc(100% - 32px));height:fit-content;max-height:calc(100dvh - 48px);margin:auto;border-radius:18px;
  overflow-y:auto;overscroll-behavior:contain;opacity:0;scale:.94}
.mo-zoom[open]{opacity:1;scale:1}
@starting-style{.mo-zoom:modal{opacity:0;scale:.94}}
.mo-invite{display:grid;grid-template-columns:minmax(0,1fr) 118px auto;align-items:end;gap:10px;margin:0 0 22px}
.mo-field label{display:block;margin:0 0 6px;color:var(--mo-ink);font-size:.8rem;font-weight:600}
.mo-input{display:block;width:100%;height:42px;margin:0;padding:0 12px;border:1px solid #8c857f;border-radius:10px;background-color:#fff;color:var(--mo-ink);font:inherit;font-size:.9rem}
.mo-input::placeholder{color:#78716c;opacity:1}
.mo-input:focus-visible{outline:2px solid var(--mo-accent);outline-offset:1px;border-color:var(--mo-accent)}
select.mo-input{appearance:none;padding-right:34px;cursor:pointer;
  background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2357534e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 10 5 5 5-5'/%3E%3C/svg%3E") no-repeat right 10px center/16px}
.mo-people{list-style:none;margin:0;padding:0}
.mo-people li{display:flex;align-items:center;gap:12px;padding:10px 0;border-top:1px solid var(--mo-line)}
.mo-people img{flex:none;width:38px;height:38px;border-radius:50%;object-fit:cover}
.mo-person{flex:1;min-width:0;font-size:.875rem;line-height:1.35}
.mo-person b{display:block;font-weight:600}
.mo-person span{display:block;overflow:hidden;color:var(--mo-muted);font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}
.mo-role{flex:none;padding:3px 10px;border-radius:999px;background:#f5f5f4;color:#44403c;font-size:.75rem;font-weight:600}
.mo-role.is-owner{background:#ffedd5;color:#9a3412}

/* 2. Slide-over panel: full height from the right, slides in and back out */
.mo-panel{--mo-dur:.34s;--mo-ease:cubic-bezier(.3,0,.2,1);position:fixed;inset:0 0 0 auto;width:min(420px,100%);height:100%;max-height:none;margin:0;border-radius:0;
  box-shadow:-30px 0 60px -30px rgba(28,25,23,.5);translate:100% 0}
.mo-panel[open]{translate:0 0}
@starting-style{.mo-panel:modal{translate:100% 0}}
.mo-panel .mo-head{padding-bottom:16px;border-bottom:1px solid var(--mo-line)}
.mo-feed{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:18px 24px 20px}
.mo-feed:focus-visible{outline:2px solid var(--mo-accent);outline-offset:-4px}
.mo-feed .mo-h3:not(:first-child){margin-top:18px}
.mo-events{list-style:none;margin:0;padding:0}
.mo-events li{position:relative;display:flex;align-items:flex-start;gap:12px;padding:10px 0}
.mo-events li:not(:last-child)::before{content:"";position:absolute;top:50px;bottom:-6px;left:17px;width:2px;border-radius:2px;background:var(--mo-line)}
.mo-av{position:relative;flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:#ffedd5;color:#9a3412;font-size:.75rem;font-weight:700}
.mo-av img{width:100%;height:100%;border-radius:50%;object-fit:cover}
.is-new .mo-av::after{content:"";position:absolute;top:-1px;right:-1px;width:11px;height:11px;border-radius:50%;background:#ea580c;box-shadow:0 0 0 2px #fff}
.mo-event{flex:1;min-width:0}
.mo-event p{margin:0;color:var(--mo-muted);font-size:.875rem;line-height:1.45}
.mo-event b,.mo-event strong{color:var(--mo-ink);font-weight:600}
.mo-event time{display:block;margin-top:2px;color:var(--mo-muted);font-size:.78rem}
.mo-event .mo-quote{margin:6px 0 0;padding:7px 12px;border-left:3px solid #fb923c;border-radius:0 8px 8px 0;background:var(--mo-soft);color:var(--mo-ink);font-size:.85rem}

/* 3. Pop-up sheet: a card anchored to the bottom that rises in and drops back out */
.mo-sheet{--mo-dur:.34s;--mo-ease:cubic-bezier(.3,0,.2,1);position:fixed;inset:auto 0 16px;width:min(560px,calc(100% - 24px));height:fit-content;max-height:calc(100dvh - 32px);margin:0 auto;
  border-radius:22px;overflow-y:auto;overscroll-behavior:contain;translate:0 calc(100% + 32px)}
.mo-sheet[open]{translate:0 0}
@starting-style{.mo-sheet:modal{translate:0 calc(100% + 32px)}}
.mo-sheet .mo-icon{border-radius:50%}
.mo-choices{margin:0;padding:0;border:1px solid var(--mo-line);border-radius:14px}
.mo-choice{display:flex;align-items:flex-start;gap:16px;padding:14px 16px}
.mo-choice+.mo-choice{border-top:1px solid var(--mo-line)}
.mo-choice-text{flex:1;min-width:0}
.mo-choice label{display:flex;align-items:center;gap:8px;font-weight:600;cursor:pointer}
.mo-choice p{margin:2px 0 0;color:var(--mo-muted);font-size:.85rem;line-height:1.45}
.mo-pill{padding:1px 8px;border-radius:999px;background:#f5f5f4;color:#44403c;font-size:.7rem;font-weight:600}
.mo-switch{flex:none;width:40px;height:24px;margin:2px 0 0;border-radius:999px;appearance:none;cursor:pointer;
  background:#78716c radial-gradient(circle,#fff 7.5px,transparent 8px) no-repeat left center/24px 24px;transition:background-color .2s,background-position .2s}
.mo-switch:checked{background-color:var(--mo-accent);background-position:right center}
.mo-switch:disabled{opacity:.55;cursor:not-allowed}
.mo-switch:focus-visible{outline:2px solid var(--mo-accent);outline-offset:2px}

@media (max-width:600px){
  .mo-zoom{max-height:calc(100dvh - 32px)}
  .mo-head,.mo-body,.mo-foot{padding-left:18px;padding-right:18px}
  .mo-feed{padding-left:18px;padding-right:18px}
  .mo-invite{grid-template-columns:minmax(0,1fr) auto}
  .mo-invite .mo-field:first-child{grid-column:1/-1}
  .mo-foot .mo-btn{flex:1 1 auto}
  .mo-sheet{inset:auto 0 0;width:100%;max-height:calc(100dvh - 16px);border-radius:22px 22px 0 0}
}
@media (prefers-reduced-motion:reduce){
  .mo,.mo::backdrop,.mo-btn,.mo-x,.mo-switch{transition:none}
}
JavaScript
// Everything above works without this script in browsers with invoker commands and closedby.
// It only fills the gaps in older browsers, and does nothing where those features exist.
// Focus goes back to the button that opened a dialog natively when the dialog closes.
if (!('command' in HTMLButtonElement.prototype)) {
  // No invoker commands: wire command="show-modal" / command="close" buttons to the dialog API
  document.addEventListener('click', function (e) {
    var btn = e.target.closest && e.target.closest('button[commandfor]');
    var dialog = btn && document.getElementById(btn.getAttribute('commandfor'));
    if (!dialog) return;
    if (btn.getAttribute('command') === 'show-modal' && !dialog.open) dialog.showModal();
    else if (btn.getAttribute('command') === 'close') dialog.close();
  });
}
if (!('closedBy' in HTMLDialogElement.prototype)) {
  // No closedby: give closedby="any" dialogs their light dismiss (a click outside the box, on the backdrop)
  document.querySelectorAll('dialog[closedby="any"]').forEach(function (dialog) {
    dialog.addEventListener('click', function (e) {
      var r = dialog.getBoundingClientRect();
      if (e.target === dialog && (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom)) dialog.close();
    });
  });
}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="mo-i-invite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9.5" cy="8" r="3.5"/><path d="M3.5 19.5c.7-3.4 3-5 6-5s5.3 1.6 6 5M18.5 8v6M15.5 11h6"/></symbol>
  <symbol id="mo-i-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 12h4l2.5-6.5 4 13 2.5-6.5h4"/></symbol>
  <symbol id="mo-i-cookie" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 12.5a8.5 8.5 0 1 1-9-9 3 3 0 0 0 3.5 3.5 3 3 0 0 0 3 3 2.5 2.5 0 0 0 2.5 2.5z"/><path d="M8.5 9.5h.01M9.5 15h.01M14.5 14.5h.01" stroke-width="2.6"/></symbol>
</svg>

<div><span class="lbl">Zoom and fade</span>
<div class="d-flex flex-wrap gap-2"><button class="btn mo-btn btn-primary" type="button" commandfor="mo-invite" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-invite"/></svg>Invite people</button></div></div>

<div><span class="lbl">Slide-over panel</span>
<div class="d-flex flex-wrap gap-2"><button class="btn mo-btn" type="button" commandfor="mo-activity" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-pulse"/></svg>Activity</button></div></div>

<div><span class="lbl">Pop-up sheet</span>
<div class="d-flex flex-wrap gap-2"><button class="btn mo-btn" type="button" commandfor="mo-cookies" command="show-modal" aria-haspopup="dialog"><svg aria-hidden="true"><use href="#mo-i-cookie"/></svg>Cookie settings</button></div></div>

<!-- 1. Zoom and fade. closedby="closerequest": Esc closes it, a stray click outside does not, so a half-typed address survives. -->
<dialog class="mo mo-zoom" id="mo-invite" closedby="closerequest" aria-labelledby="mo-invite-title">
  <div class="modal-header">
    <span class="mo-icon" aria-hidden="true"><svg><use href="#mo-i-invite"/></svg></span>
    <div class="flex-grow-1"><h2 class="modal-title" id="mo-invite-title">Invite people to Northfield</h2><p class="mo-sub">They get an email with a link to join the workspace.</p></div>
    <button class="btn-close" type="button" commandfor="mo-invite" command="close" aria-label="Close"></button>
  </div>
  <div class="modal-body">
    <form class="row align-items-end mo-invite" method="dialog">
      <div class="col-12 col-sm"><label class="form-label" for="mo-invite-email">Email address</label><input class="form-control" id="mo-invite-email" name="email" type="email" placeholder="[email protected]" autocomplete="off" required autofocus></div>
      <div class="col col-sm-auto"><label class="form-label" for="mo-invite-role">Role</label><select class="form-select mo-role" id="mo-invite-role" name="role"><option>Viewer</option><option selected>Editor</option><option>Admin</option></select></div>
      <div class="col-auto"><button class="btn mo-btn btn-primary" value="invite">Send invite</button></div>
    </form>
    <h3 class="mo-h3">People with access</h3>
    <ul class="list-group list-group-flush mo-people">
      <li class="list-group-item d-flex align-items-center"><img class="rounded-circle" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Hana Kobayashi"><div class="flex-grow-1 overflow-hidden"><b class="d-block fw-semibold">Hana Kobayashi</b><span class="d-block text-truncate text-body-secondary">[email protected]</span></div><span class="badge rounded-pill mo-badge is-owner">Owner</span></li>
      <li class="list-group-item d-flex align-items-center"><img class="rounded-circle" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Theo Marchetti"><div class="flex-grow-1 overflow-hidden"><b class="d-block fw-semibold">Theo Marchetti</b><span class="d-block text-truncate text-body-secondary">[email protected]</span></div><span class="badge rounded-pill mo-badge">Editor</span></li>
      <li class="list-group-item d-flex align-items-center"><img class="rounded-circle" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Priya Raman"><div class="flex-grow-1 overflow-hidden"><b class="d-block fw-semibold">Priya Raman</b><span class="d-block text-truncate text-body-secondary">[email protected]</span></div><span class="badge rounded-pill mo-badge">Viewer</span></li>
    </ul>
  </div>
  <div class="modal-footer">
    <p class="me-auto mb-0 small text-body-secondary">3 of 10 seats used</p>
    <button class="btn mo-btn" type="button" commandfor="mo-invite" command="close">Done</button>
  </div>
</dialog>

<!-- 2. Slide-over panel. closedby="any": light dismiss by a backdrop click or Esc, nothing to lose. -->
<dialog class="mo mo-panel" id="mo-activity" closedby="any" aria-labelledby="mo-activity-title">
  <div class="modal-header">
    <div class="flex-grow-1"><h2 class="modal-title" id="mo-activity-title">Activity</h2><p class="mo-sub">Northfield Ceramics · 3 unread</p></div>
    <button class="btn-close" type="button" commandfor="mo-activity" command="close" aria-label="Close"></button>
  </div>
  <div class="modal-body" tabindex="0" role="region" aria-label="Activity feed">
    <h3 class="mo-h3">Today</h3>
    <ol class="list-unstyled mb-0 mo-events">
      <li class="d-flex align-items-start is-new"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="flex-grow-1"><p><span class="visually-hidden">Unread: </span><b>Hana Kobayashi</b> published <strong>Autumn glaze catalogue</strong></p><time datetime="2026-09-25T09:42">9:42</time></div></li>
      <li class="d-flex align-items-start is-new"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="flex-grow-1"><p><span class="visually-hidden">Unread: </span><b>Theo Marchetti</b> commented on <strong>Kiln schedule, week 40</strong></p><p class="mo-quote">Firing moves to Thursday. The kiln is booked for the tile order on Wednesday.</p><time datetime="2026-09-25T09:15">9:15</time></div></li>
      <li class="d-flex align-items-start is-new"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="flex-grow-1"><p><span class="visually-hidden">Unread: </span><b>Priya Raman</b> uploaded 12 photos to <strong>Stoneware mugs</strong></p><time datetime="2026-09-25T08:58">8:58</time></div></li>
      <li class="d-flex align-items-start"><span class="mo-av rounded-circle" aria-hidden="true">JW</span><div class="flex-grow-1"><p><b>Jonas Weber</b> invited [email protected] as a Viewer</p><time datetime="2026-09-25T08:31">8:31</time></div></li>
      <li class="d-flex align-items-start"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="flex-grow-1"><p><b>Hana Kobayashi</b> changed the price of <strong>Speckled bowl, large</strong> to £34</p><time datetime="2026-09-25T08:03">8:03</time></div></li>
    </ol>
    <h3 class="mo-h3">Yesterday</h3>
    <ol class="list-unstyled mb-0 mo-events">
      <li class="d-flex align-items-start"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="flex-grow-1"><p><b>Theo Marchetti</b> completed <strong>Order 1187: pack and ship</strong></p><time datetime="2026-09-24T17:40">17:40</time></div></li>
      <li class="d-flex align-items-start"><span class="mo-av rounded-circle" aria-hidden="true">AM</span><div class="flex-grow-1"><p><b>Ama Mensah</b> joined the workspace as an Editor</p><time datetime="2026-09-24T16:12">16:12</time></div></li>
      <li class="d-flex align-items-start"><span class="mo-av rounded-circle"><img class="rounded-circle" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="flex-grow-1"><p><b>Priya Raman</b> archived <strong>Summer market 2026</strong></p><time datetime="2026-09-24T11:05">11:05</time></div></li>
    </ol>
  </div>
  <form class="modal-footer" method="dialog">
    <a class="btn mo-btn" href="#">Full log</a>
    <button class="btn mo-btn btn-primary" value="read">Mark all as read</button>
  </form>
</dialog>

<!-- 3. Pop-up sheet. closedby="closerequest": no light dismiss, because a click on the page behind is not a consent choice.
     Esc still closes it, and saves nothing. -->
<dialog class="mo mo-sheet" id="mo-cookies" closedby="closerequest" aria-labelledby="mo-cookies-title" aria-describedby="mo-cookies-desc">
  <form class="d-flex flex-column flex-grow-1 m-0" method="dialog">
    <div class="modal-header">
      <span class="mo-icon" aria-hidden="true"><svg><use href="#mo-i-cookie"/></svg></span>
      <div class="flex-grow-1"><h2 class="modal-title" id="mo-cookies-title">Cookie settings</h2><p class="mo-sub" id="mo-cookies-desc">Choose which cookies Northfield Ceramics may use. You can change this at any time from the link in the footer.</p></div>
    </div>
    <div class="modal-body">
      <fieldset class="mo-choices">
        <legend class="visually-hidden">Cookie categories</legend>
        <div class="form-check form-switch mo-choice">
          <div class="flex-grow-1"><label class="form-check-label" for="mo-ck-necessary">Necessary <span class="badge rounded-pill mo-pill">Always on</span></label><p id="mo-ck-necessary-desc">Keep you signed in, remember your basket and protect the checkout.</p></div>
          <input class="form-check-input" type="checkbox" role="switch" id="mo-ck-necessary" aria-describedby="mo-ck-necessary-desc" checked disabled>
        </div>
        <div class="form-check form-switch mo-choice">
          <div class="flex-grow-1"><label class="form-check-label" for="mo-ck-analytics">Analytics</label><p id="mo-ck-analytics-desc">Count visits and see which pages are read, so we can fix what is slow or confusing.</p></div>
          <input class="form-check-input" type="checkbox" role="switch" id="mo-ck-analytics" name="analytics" aria-describedby="mo-ck-analytics-desc">
        </div>
        <div class="form-check form-switch mo-choice">
          <div class="flex-grow-1"><label class="form-check-label" for="mo-ck-marketing">Marketing</label><p id="mo-ck-marketing-desc">Show our ads on other sites and measure whether they lead to an order.</p></div>
          <input class="form-check-input" type="checkbox" role="switch" id="mo-ck-marketing" name="marketing" aria-describedby="mo-ck-marketing-desc">
        </div>
      </fieldset>
    </div>
    <div class="modal-footer">
      <button class="btn mo-btn" value="reject">Reject all</button>
      <button class="btn mo-btn btn-primary" value="save">Save choices</button>
    </div>
  </form>
</dialog>
CSS
/* Bootstrap version of the modern-CSS modals. Bootstrap's Modal plugin can't be driven by invoker commands, so this set
   uses the same native <dialog> as the plain version (no Bootstrap JavaScript) and borrows Bootstrap's look:
   .modal-header/.modal-body/.modal-footer (fed by --bs-modal-* variables), .btn, .btn-close, .form-control, .form-select,
   .form-switch, .list-group and .badge. Enter and exit animations are CSS only: @starting-style + allow-discrete on
   display and overlay. @starting-style is keyed to :modal, so a static copy with [open] renders in its final state. */
html{scrollbar-gutter:stable}          /* the page does not shift sideways when the scroll lock hides its scrollbar */
html:has(.mo:modal){overflow:hidden}   /* scroll lock while one of these dialogs is open */

.mo-btn{--bs-btn-padding-x:16px;--bs-btn-padding-y:9px;--bs-btn-font-size:.9rem;--bs-btn-font-weight:600;--bs-btn-line-height:1.2;--bs-btn-border-radius:10px;
  --bs-btn-bg:#fff;--bs-btn-color:#1c1917;--bs-btn-border-color:#d6d3d1;--bs-btn-hover-bg:#f5f5f4;--bs-btn-hover-color:#1c1917;--bs-btn-hover-border-color:#a8a29e;
  --bs-btn-active-bg:#e7e5e4;--bs-btn-active-color:#1c1917;--bs-btn-active-border-color:#a8a29e;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:42px;white-space:nowrap}
.mo-btn.btn-primary{--bs-btn-bg:#c2410c;--bs-btn-color:#fff;--bs-btn-border-color:#c2410c;--bs-btn-hover-bg:#9a3412;--bs-btn-hover-color:#fff;--bs-btn-hover-border-color:#9a3412;
  --bs-btn-active-bg:#7c2d12;--bs-btn-active-color:#fff;--bs-btn-active-border-color:#7c2d12;box-shadow:0 6px 16px -8px rgba(234,88,12,.8)}
.mo-btn:focus-visible{outline:2px solid #c2410c;outline-offset:2px;box-shadow:none}
.mo-btn svg{width:18px;height:18px;flex:none}

/* The dialog shell, shared by all three variants. Bootstrap's body and modal variables are scoped to it. */
.mo{--bs-body-color:#1c1917;--bs-secondary-color:#57534e;--bs-border-color:#e7e5e4;
  --bs-modal-padding:24px;--bs-modal-header-padding:22px 24px 6px;--bs-modal-header-padding-x:24px;--bs-modal-header-padding-y:22px;--bs-modal-header-border-width:0;
  --bs-modal-header-border-color:#e7e5e4;--bs-modal-inner-border-radius:0;--bs-modal-title-line-height:1.35;--bs-modal-footer-gap:10px;--bs-modal-footer-bg:#fafaf9;
  --bs-modal-footer-border-color:#e7e5e4;--bs-modal-footer-border-width:1px;--mo-dur:.26s;--mo-ease:cubic-bezier(.2,.8,.2,1);
  flex-direction:column;max-width:none;padding:0;border:0;background:#fff;color:#1c1917;font-size:.95rem;line-height:1.55;overflow:hidden;
  box-shadow:0 0 0 1px rgba(28,25,23,.06),0 30px 70px -22px rgba(28,25,23,.5);
  transition:opacity var(--mo-dur) var(--mo-ease),scale var(--mo-dur) var(--mo-ease),translate var(--mo-dur) var(--mo-ease),
    display var(--mo-dur) allow-discrete,overlay var(--mo-dur) allow-discrete}
.mo[open]{display:flex}
.mo::backdrop{background-color:rgb(28 25 23 / 0);backdrop-filter:blur(0);
  transition:background-color .26s,backdrop-filter .26s,display .26s allow-discrete,overlay .26s allow-discrete}
.mo[open]::backdrop{background-color:rgb(28 25 23 / .5);backdrop-filter:blur(3px)}
@starting-style{.mo:modal::backdrop{background-color:rgb(28 25 23 / 0);backdrop-filter:blur(0)}}

.mo .modal-header{align-items:flex-start;gap:14px}
.mo .modal-title{font-size:1.15rem;font-weight:700;letter-spacing:-.01em}
.mo-sub{margin:3px 0 0;color:#57534e;font-size:.85rem}
.mo-icon{flex:none;display:grid;place-items:center;width:42px;height:42px;border-radius:12px;background:linear-gradient(135deg,#fff7ed,#ffedd5);color:#c2410c}
.mo-icon svg{width:22px;height:22px}
.mo .modal-header .btn-close{flex:none;width:36px;height:36px;margin:-4px -8px 0 auto;padding:0;border-radius:10px;background-size:13px;opacity:.75}
.mo .btn-close:hover{background-color:#f5f5f4;opacity:1}
.mo .btn-close:focus-visible{outline:2px solid #c2410c;outline-offset:1px;box-shadow:none;opacity:1}
.mo .modal-body{padding:16px 24px 22px}
.mo .modal-footer{gap:10px;padding:14px 24px}
.mo .modal-footer>*{margin:0}
.mo-h3{margin:0 0 6px;line-height:1.55;color:#57534e;font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}

/* 1. Zoom and fade: scales up from .94 and fades in, and does the reverse on close */
.mo-zoom{position:fixed;inset:0;width:min(500px,calc(100% - 32px));height:fit-content;max-height:calc(100dvh - 48px);margin:auto;border-radius:18px;
  overflow-y:auto;overscroll-behavior:contain;opacity:0;scale:.94}
.mo-zoom[open]{opacity:1;scale:1}
@starting-style{.mo-zoom:modal{opacity:0;scale:.94}}
.mo-invite{--bs-gutter-x:10px;--bs-gutter-y:10px;margin-bottom:22px}
@media (min-width:576px){.mo-invite .mo-role{width:118px}}
.mo .form-label{margin-bottom:6px;color:#1c1917;font-size:.8rem;font-weight:600}
.mo .form-control,.mo .form-select{min-height:42px;border-color:#8c857f;border-radius:10px;color:#1c1917;font-size:.9rem}
.mo .form-control::placeholder{color:#78716c}
.mo .form-control:focus,.mo .form-select:focus{border-color:#c2410c;box-shadow:none}
.mo .form-control:focus-visible,.mo .form-select:focus-visible{outline:2px solid #c2410c;outline-offset:1px}
.mo-people{--bs-list-group-bg:transparent;--bs-list-group-color:#1c1917;--bs-list-group-border-color:#e7e5e4;--bs-list-group-item-padding-x:0;--bs-list-group-item-padding-y:10px;
  border-top:1px solid #e7e5e4}
.mo-people .list-group-item{gap:12px;font-size:.875rem;line-height:1.35}
.mo-people img{flex:none;object-fit:cover}
.mo-people .text-truncate{font-size:.8rem}
.mo .mo-badge{padding:5px 10px;background:#f5f5f4;color:#44403c;font-size:.75rem;font-weight:600}
.mo .mo-badge.is-owner{background:#ffedd5;color:#9a3412}

/* 2. Slide-over panel: full height from the right, slides in and back out */
.mo-panel{--mo-dur:.34s;--mo-ease:cubic-bezier(.3,0,.2,1);position:fixed;inset:0 0 0 auto;width:min(420px,100%);height:100%;max-height:none;margin:0;border-radius:0;
  box-shadow:-30px 0 60px -30px rgba(28,25,23,.5);translate:100% 0}
.mo-panel[open]{translate:0 0}
@starting-style{.mo-panel:modal{translate:100% 0}}
.mo-panel .modal-header{padding-bottom:16px;border-bottom:1px solid #e7e5e4}
.mo-panel .modal-body{min-height:0;overflow-y:auto;overscroll-behavior:contain;padding-top:18px;padding-bottom:20px}
.mo-panel .modal-body:focus-visible{outline:2px solid #c2410c;outline-offset:-4px}
.mo-panel .modal-body .mo-h3:not(:first-child){margin-top:18px}
.mo-events li{position:relative;gap:12px;padding:10px 0}
.mo-events li:not(:last-child)::before{content:"";position:absolute;top:50px;bottom:-6px;left:17px;width:2px;border-radius:2px;background:#e7e5e4}
.mo-av{position:relative;flex:none;display:grid;place-items:center;width:36px;height:36px;background:#ffedd5;color:#9a3412;font-size:.75rem;font-weight:700}
.mo-av img{width:100%;height:100%;object-fit:cover}
.is-new .mo-av::after{content:"";position:absolute;top:-1px;right:-1px;width:11px;height:11px;border-radius:50%;background:#ea580c;box-shadow:0 0 0 2px #fff}
.mo-events p{margin:0;color:#57534e;font-size:.875rem;line-height:1.45}
.mo-events b,.mo-events strong{color:#1c1917;font-weight:600}
.mo-events time{display:block;margin-top:2px;color:#57534e;font-size:.78rem}
.mo-events .mo-quote{margin:6px 0 0;padding:7px 12px;border-left:3px solid #fb923c;border-radius:0 8px 8px 0;background:#fafaf9;color:#1c1917;font-size:.85rem}

/* 3. Pop-up sheet: a card anchored to the bottom that rises in and drops back out */
.mo-sheet{--mo-dur:.34s;--mo-ease:cubic-bezier(.3,0,.2,1);position:fixed;inset:auto 0 16px;width:min(560px,calc(100% - 24px));height:fit-content;max-height:calc(100dvh - 32px);margin:0 auto;
  border-radius:22px;overflow-y:auto;overscroll-behavior:contain;translate:0 calc(100% + 32px)}
.mo-sheet[open]{translate:0 0}
@starting-style{.mo-sheet:modal{translate:0 calc(100% + 32px)}}
.mo-sheet .mo-icon{border-radius:50%}
.mo-choices{border:1px solid #e7e5e4;border-radius:14px}
.mo-choice.form-check{display:flex;align-items:flex-start;gap:16px;min-height:0;margin:0;padding:14px 16px}
.mo-choice+.mo-choice{border-top:1px solid #e7e5e4}
.mo-choice .form-check-label{display:flex;align-items:center;gap:8px;color:#1c1917;font-weight:600;cursor:pointer}
.mo-choice p{margin:2px 0 0;color:#57534e;font-size:.85rem;line-height:1.45}
.mo .mo-pill{padding:3px 8px;background:#f5f5f4;color:#44403c;font-size:.7rem;font-weight:600}
.mo-choice .form-check-input{float:none;flex:none;width:40px;height:24px;margin:2px 0 0;border-color:#78716c;background-color:#78716c;cursor:pointer;
  --bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.9' fill='%23fff'/%3e%3c/svg%3e");
  transition:background-color .2s,background-position .2s}
.mo-choice .form-check-input:checked{border-color:#c2410c;background-color:#c2410c}
.mo-choice .form-check-input:focus{border-color:#78716c;box-shadow:none;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2.9' fill='%23fff'/%3e%3c/svg%3e")}
.mo-choice .form-check-input:checked:focus{border-color:#c2410c}
.mo-choice .form-check-input:focus-visible{outline:2px solid #c2410c;outline-offset:2px}
.mo-choice .form-check-input:disabled{opacity:.55;cursor:not-allowed}

@media (max-width:600px){
  .mo-zoom{max-height:calc(100dvh - 32px)}
  .mo{--bs-modal-header-padding:22px 18px 6px}
  .mo .modal-body,.mo .modal-footer{padding-left:18px;padding-right:18px}
  .mo .modal-footer .mo-btn{flex:1 1 auto}
  .mo-sheet{inset:auto 0 0;width:100%;max-height:calc(100dvh - 16px);border-radius:22px 22px 0 0}
}
@media (prefers-reduced-motion:reduce){
  .mo,.mo::backdrop,.mo-btn,.mo-choice .form-check-input{transition:none}
}
JavaScript
// Everything above works without this script in browsers with invoker commands and closedby.
// It only fills the gaps in older browsers, and does nothing where those features exist.
// Focus goes back to the button that opened a dialog natively when the dialog closes.
if (!('command' in HTMLButtonElement.prototype)) {
  // No invoker commands: wire command="show-modal" / command="close" buttons to the dialog API
  document.addEventListener('click', function (e) {
    var btn = e.target.closest && e.target.closest('button[commandfor]');
    var dialog = btn && document.getElementById(btn.getAttribute('commandfor'));
    if (!dialog) return;
    if (btn.getAttribute('command') === 'show-modal' && !dialog.open) dialog.showModal();
    else if (btn.getAttribute('command') === 'close') dialog.close();
  });
}
if (!('closedBy' in HTMLDialogElement.prototype)) {
  // No closedby: give closedby="any" dialogs their light dismiss (a click outside the box, on the backdrop)
  document.querySelectorAll('dialog[closedby="any"]').forEach(function (dialog) {
    dialog.addEventListener('click', function (e) {
      var r = dialog.getBoundingClientRect();
      if (e.target === dialog && (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom)) dialog.close();
    });
  });
}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="mo-i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="mo-i-invite" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9.5" cy="8" r="3.5"/><path d="M3.5 19.5c.7-3.4 3-5 6-5s5.3 1.6 6 5M18.5 8v6M15.5 11h6"/></symbol>
  <symbol id="mo-i-pulse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 12h4l2.5-6.5 4 13 2.5-6.5h4"/></symbol>
  <symbol id="mo-i-cookie" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 12.5a8.5 8.5 0 1 1-9-9 3 3 0 0 0 3.5 3.5 3 3 0 0 0 3 3 2.5 2.5 0 0 0 2.5 2.5z"/><path d="M8.5 9.5h.01M9.5 15h.01M14.5 14.5h.01" stroke-width="2.6"/></symbol>
</svg>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5f5853] uppercase">Zoom and fade</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#c2410c] bg-[#c2410c] text-white shadow-[0_6px_16px_-8px_rgba(234,88,12,.8)] hover:border-[#9a3412] hover:bg-[#9a3412]" type="button" commandfor="mo-invite" command="show-modal" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#mo-i-invite"/></svg>Invite people</button></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5f5853] uppercase">Slide-over panel</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#d6d3d1] bg-white text-[#1c1917] hover:border-[#a8a29e] hover:bg-[#f5f5f4]" type="button" commandfor="mo-activity" command="show-modal" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#mo-i-pulse"/></svg>Activity</button></div></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-[#5f5853] uppercase">Pop-up sheet</span>
<div class="flex flex-wrap gap-2.5"><button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#d6d3d1] bg-white text-[#1c1917] hover:border-[#a8a29e] hover:bg-[#f5f5f4]" type="button" commandfor="mo-cookies" command="show-modal" aria-haspopup="dialog"><svg class="size-[18px] flex-none" aria-hidden="true"><use href="#mo-i-cookie"/></svg>Cookie settings</button></div></div>

<!-- 1. Zoom and fade. closedby="closerequest": Esc closes it, a stray click outside does not, so a half-typed address survives. -->
<dialog class="fixed inset-0 m-auto h-fit max-h-[calc(100dvh-48px)] w-[min(500px,calc(100%-32px))] overflow-y-auto overscroll-contain rounded-[18px] scale-94 opacity-0 open:scale-100 open:opacity-100 starting:[&:modal]:scale-94 starting:[&:modal]:opacity-0 transition-[opacity,scale,display,overlay] duration-[260ms] ease-[cubic-bezier(.2,.8,.2,1)] max-[600px]:max-h-[calc(100dvh-32px)] max-w-none flex-col border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#1c1917] shadow-[0_0_0_1px_rgba(28,25,23,.06),0_30px_70px_-22px_rgba(28,25,23,.5)] open:flex transition-discrete backdrop:bg-[rgb(28_25_23/0)] backdrop:transition-[background-color,backdrop-filter,display,overlay] backdrop:transition-discrete backdrop:duration-[260ms] open:backdrop:bg-[rgb(28_25_23/.5)] open:backdrop:backdrop-blur-[3px] starting:[&:modal]:backdrop:bg-[rgb(28_25_23/0)] starting:[&:modal]:backdrop:backdrop-blur-none motion-reduce:transition-none motion-reduce:backdrop:transition-none" id="mo-invite" closedby="closerequest" aria-labelledby="mo-invite-title">
  <div class="flex items-start gap-3.5 px-6 pt-[22px] pb-1.5 max-[600px]:px-[18px]">
    <span class="grid size-[42px] flex-none place-items-center bg-[linear-gradient(135deg,#fff7ed,#ffedd5)] text-[#c2410c] rounded-xl" aria-hidden="true"><svg class="size-[22px]"><use href="#mo-i-invite"/></svg></span>
    <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.15rem] leading-[1.35] font-bold tracking-[-.01em]" id="mo-invite-title">Invite people to Northfield</h2><p class="mt-[3px] mb-0 text-[.85rem] text-[#57534e]">They get an email with a link to join the workspace.</p></div>
    <button class="-mt-1 -mr-2 grid size-9 flex-none cursor-pointer place-items-center rounded-[10px] border-0 bg-transparent p-0 text-[#57534e] transition-colors duration-150 hover:bg-[#f5f5f4] hover:text-[#1c1917] focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-1 motion-reduce:transition-none" type="button" commandfor="mo-invite" command="close" aria-label="Close"><svg class="size-5" aria-hidden="true"><use href="#mo-i-x"/></svg></button>
  </div>
  <div class="px-6 pt-4 pb-[22px] max-[600px]:px-[18px]">
    <form class="m-0 mb-[22px] grid grid-cols-[minmax(0,1fr)_118px_auto] items-end gap-2.5 max-[600px]:grid-cols-[minmax(0,1fr)_auto]" method="dialog">
      <div class="max-[600px]:col-span-full"><label class="mb-1.5 block text-[.8rem] font-semibold text-[#1c1917]" for="mo-invite-email">Email address</label><input class="m-0 block h-[42px] w-full rounded-[10px] border border-[#8c857f] bg-white px-3 text-[.9rem] text-[#1c1917] placeholder:text-[#78716c] focus-visible:border-[#c2410c] focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-1" id="mo-invite-email" name="email" type="email" placeholder="[email protected]" autocomplete="off" required autofocus></div>
      <div><label class="mb-1.5 block text-[.8rem] font-semibold text-[#1c1917]" for="mo-invite-role">Role</label><div class="relative"><select class="m-0 block h-[42px] w-full rounded-[10px] border border-[#8c857f] bg-white px-3 text-[.9rem] text-[#1c1917] placeholder:text-[#78716c] focus-visible:border-[#c2410c] focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-1 cursor-pointer appearance-none pr-[34px]" id="mo-invite-role" name="role"><option>Viewer</option><option selected>Editor</option><option>Admin</option></select><svg class="pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-[#57534e]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 10 5 5 5-5"/></svg></div></div>
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#c2410c] bg-[#c2410c] text-white shadow-[0_6px_16px_-8px_rgba(234,88,12,.8)] hover:border-[#9a3412] hover:bg-[#9a3412]" value="invite">Send invite</button>
    </form>
    <h3 class="mt-0 mb-1.5 text-[.72rem] font-bold tracking-[.08em] text-[#57534e] uppercase">People with access</h3>
    <ul class="m-0 list-none p-0">
      <li class="flex items-center gap-3 border-t border-[#e7e5e4] py-2.5"><img class="size-[38px] flex-none rounded-full object-cover" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Hana Kobayashi"><div class="min-w-0 flex-1 text-[.875rem] leading-[1.35]"><b class="block font-semibold">Hana Kobayashi</b><span class="block truncate text-[.8rem] text-[#57534e]">[email protected]</span></div><span class="flex-none rounded-full px-2.5 py-[3px] text-[.75rem] font-semibold bg-[#ffedd5] text-[#9a3412]">Owner</span></li>
      <li class="flex items-center gap-3 border-t border-[#e7e5e4] py-2.5"><img class="size-[38px] flex-none rounded-full object-cover" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Theo Marchetti"><div class="min-w-0 flex-1 text-[.875rem] leading-[1.35]"><b class="block font-semibold">Theo Marchetti</b><span class="block truncate text-[.8rem] text-[#57534e]">[email protected]</span></div><span class="flex-none rounded-full px-2.5 py-[3px] text-[.75rem] font-semibold bg-[#f5f5f4] text-[#44403c]">Editor</span></li>
      <li class="flex items-center gap-3 border-t border-[#e7e5e4] py-2.5"><img class="size-[38px] flex-none rounded-full object-cover" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="38" height="38" alt="Priya Raman"><div class="min-w-0 flex-1 text-[.875rem] leading-[1.35]"><b class="block font-semibold">Priya Raman</b><span class="block truncate text-[.8rem] text-[#57534e]">[email protected]</span></div><span class="flex-none rounded-full px-2.5 py-[3px] text-[.75rem] font-semibold bg-[#f5f5f4] text-[#44403c]">Viewer</span></li>
    </ul>
  </div>
  <div class="m-0 flex flex-wrap items-center justify-end gap-2.5 border-t border-[#e7e5e4] bg-[#fafaf9] px-6 py-3.5 max-[600px]:px-[18px]">
    <p class="m-0 mr-auto text-[.85rem] text-[#57534e]">3 of 10 seats used</p>
    <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#d6d3d1] bg-white text-[#1c1917] hover:border-[#a8a29e] hover:bg-[#f5f5f4] max-[600px]:flex-auto" type="button" commandfor="mo-invite" command="close">Done</button>
  </div>
</dialog>

<!-- 2. Slide-over panel. closedby="any": light dismiss by a backdrop click or Esc, nothing to lose. -->
<dialog class="fixed inset-y-0 right-0 left-auto m-0 h-full max-h-none w-[min(420px,100%)] overflow-hidden rounded-none shadow-[-30px_0_60px_-30px_rgba(28,25,23,.5)] [translate:100%_0] open:[translate:0_0] starting:[&:modal]:[translate:100%_0] transition-[translate,display,overlay] duration-[340ms] ease-[cubic-bezier(.3,0,.2,1)] max-w-none flex-col border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#1c1917] open:flex transition-discrete backdrop:bg-[rgb(28_25_23/0)] backdrop:transition-[background-color,backdrop-filter,display,overlay] backdrop:transition-discrete backdrop:duration-[260ms] open:backdrop:bg-[rgb(28_25_23/.5)] open:backdrop:backdrop-blur-[3px] starting:[&:modal]:backdrop:bg-[rgb(28_25_23/0)] starting:[&:modal]:backdrop:backdrop-blur-none motion-reduce:transition-none motion-reduce:backdrop:transition-none" id="mo-activity" closedby="any" aria-labelledby="mo-activity-title">
  <div class="flex items-start gap-3.5 px-6 pt-[22px] pb-1.5 max-[600px]:px-[18px] border-b border-[#e7e5e4] pb-4">
    <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.15rem] leading-[1.35] font-bold tracking-[-.01em]" id="mo-activity-title">Activity</h2><p class="mt-[3px] mb-0 text-[.85rem] text-[#57534e]">Northfield Ceramics · 3 unread</p></div>
    <button class="-mt-1 -mr-2 grid size-9 flex-none cursor-pointer place-items-center rounded-[10px] border-0 bg-transparent p-0 text-[#57534e] transition-colors duration-150 hover:bg-[#f5f5f4] hover:text-[#1c1917] focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-1 motion-reduce:transition-none" type="button" commandfor="mo-activity" command="close" aria-label="Close"><svg class="size-5" aria-hidden="true"><use href="#mo-i-x"/></svg></button>
  </div>
  <div class="min-h-0 flex-auto overflow-y-auto overscroll-contain px-6 pt-[18px] pb-5 focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-[#c2410c] max-[600px]:px-[18px]" tabindex="0" role="region" aria-label="Activity feed">
    <h3 class="mt-0 mb-1.5 text-[.72rem] font-bold tracking-[.08em] text-[#57534e] uppercase">Today</h3>
    <ol class="m-0 list-none p-0">
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412] after:absolute after:-top-px after:-right-px after:size-[11px] after:rounded-full after:bg-[#ea580c] after:shadow-[0_0_0_2px_#fff] after:content-['']"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><span class="sr-only">Unread: </span><b class="font-semibold text-[#1c1917]">Hana Kobayashi</b> published <strong class="font-semibold text-[#1c1917]">Autumn glaze catalogue</strong></p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-25T09:42">9:42</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412] after:absolute after:-top-px after:-right-px after:size-[11px] after:rounded-full after:bg-[#ea580c] after:shadow-[0_0_0_2px_#fff] after:content-['']"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><span class="sr-only">Unread: </span><b class="font-semibold text-[#1c1917]">Theo Marchetti</b> commented on <strong class="font-semibold text-[#1c1917]">Kiln schedule, week 40</strong></p><p class="mt-1.5 mb-0 rounded-r-lg border-l-[3px] border-[#fb923c] bg-[#fafaf9] px-3 py-[7px] text-[.85rem] text-[#1c1917]">Firing moves to Thursday. The kiln is booked for the tile order on Wednesday.</p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-25T09:15">9:15</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412] after:absolute after:-top-px after:-right-px after:size-[11px] after:rounded-full after:bg-[#ea580c] after:shadow-[0_0_0_2px_#fff] after:content-['']"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><span class="sr-only">Unread: </span><b class="font-semibold text-[#1c1917]">Priya Raman</b> uploaded 12 photos to <strong class="font-semibold text-[#1c1917]">Stoneware mugs</strong></p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-25T08:58">8:58</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412]" aria-hidden="true">JW</span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><b class="font-semibold text-[#1c1917]">Jonas Weber</b> invited [email protected] as a Viewer</p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-25T08:31">8:31</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412]"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Hana Kobayashi"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><b class="font-semibold text-[#1c1917]">Hana Kobayashi</b> changed the price of <strong class="font-semibold text-[#1c1917]">Speckled bowl, large</strong> to £34</p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-25T08:03">8:03</time></div></li>
    </ol>
    <h3 class="mt-0 mb-1.5 text-[.72rem] font-bold tracking-[.08em] text-[#57534e] uppercase mt-[18px]">Yesterday</h3>
    <ol class="m-0 list-none p-0">
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412]"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Theo Marchetti"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><b class="font-semibold text-[#1c1917]">Theo Marchetti</b> completed <strong class="font-semibold text-[#1c1917]">Order 1187: pack and ship</strong></p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-24T17:40">17:40</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412]" aria-hidden="true">AM</span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><b class="font-semibold text-[#1c1917]">Ama Mensah</b> joined the workspace as an Editor</p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-24T16:12">16:12</time></div></li>
      <li class="relative flex items-start gap-3 py-2.5 not-last:before:absolute not-last:before:top-[50px] not-last:before:-bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-[#e7e5e4] not-last:before:content-['']"><span class="relative grid size-9 flex-none place-items-center rounded-full bg-[#ffedd5] text-[.75rem] font-bold text-[#9a3412]"><img class="size-full rounded-full object-cover" src="https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&crop=faces&w=96&h=96&q=70" width="36" height="36" alt="Priya Raman"></span><div class="min-w-0 flex-1"><p class="m-0 text-[.875rem] leading-[1.45] text-[#57534e]"><b class="font-semibold text-[#1c1917]">Priya Raman</b> archived <strong class="font-semibold text-[#1c1917]">Summer market 2026</strong></p><time class="mt-0.5 block text-[.78rem] text-[#57534e]" datetime="2026-09-24T11:05">11:05</time></div></li>
    </ol>
  </div>
  <form class="m-0 flex flex-wrap items-center justify-end gap-2.5 border-t border-[#e7e5e4] bg-[#fafaf9] px-6 py-3.5 max-[600px]:px-[18px]" method="dialog">
    <a class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#d6d3d1] bg-white text-[#1c1917] hover:border-[#a8a29e] hover:bg-[#f5f5f4] max-[600px]:flex-auto" href="#">Full log</a>
    <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#c2410c] bg-[#c2410c] text-white shadow-[0_6px_16px_-8px_rgba(234,88,12,.8)] hover:border-[#9a3412] hover:bg-[#9a3412] max-[600px]:flex-auto" value="read">Mark all as read</button>
  </form>
</dialog>

<!-- 3. Pop-up sheet. closedby="closerequest": no light dismiss, because a click on the page behind is not a consent choice.
     Esc still closes it, and saves nothing. -->
<dialog class="fixed inset-x-0 top-auto bottom-4 mx-auto my-0 h-fit max-h-[calc(100dvh-32px)] w-[min(560px,calc(100%-24px))] overflow-y-auto overscroll-contain rounded-[22px] [translate:0_calc(100%_+_32px)] open:[translate:0_0] starting:[&:modal]:[translate:0_calc(100%_+_32px)] transition-[translate,display,overlay] duration-[340ms] ease-[cubic-bezier(.3,0,.2,1)] max-[600px]:bottom-0 max-[600px]:max-h-[calc(100dvh-16px)] max-[600px]:w-full max-[600px]:rounded-b-none max-w-none flex-col border-0 bg-white p-0 text-[.95rem] leading-[1.55] text-[#1c1917] shadow-[0_0_0_1px_rgba(28,25,23,.06),0_30px_70px_-22px_rgba(28,25,23,.5)] open:flex transition-discrete backdrop:bg-[rgb(28_25_23/0)] backdrop:transition-[background-color,backdrop-filter,display,overlay] backdrop:transition-discrete backdrop:duration-[260ms] open:backdrop:bg-[rgb(28_25_23/.5)] open:backdrop:backdrop-blur-[3px] starting:[&:modal]:backdrop:bg-[rgb(28_25_23/0)] starting:[&:modal]:backdrop:backdrop-blur-none motion-reduce:transition-none motion-reduce:backdrop:transition-none" id="mo-cookies" closedby="closerequest" aria-labelledby="mo-cookies-title" aria-describedby="mo-cookies-desc">
  <form class="m-0 flex min-h-0 flex-1 flex-col" method="dialog">
    <div class="flex items-start gap-3.5 px-6 pt-[22px] pb-1.5 max-[600px]:px-[18px]">
      <span class="grid size-[42px] flex-none place-items-center bg-[linear-gradient(135deg,#fff7ed,#ffedd5)] text-[#c2410c] rounded-full" aria-hidden="true"><svg class="size-[22px]"><use href="#mo-i-cookie"/></svg></span>
      <div class="min-w-0 flex-1"><h2 class="m-0 text-[1.15rem] leading-[1.35] font-bold tracking-[-.01em]" id="mo-cookies-title">Cookie settings</h2><p class="mt-[3px] mb-0 text-[.85rem] text-[#57534e]" id="mo-cookies-desc">Choose which cookies Northfield Ceramics may use. You can change this at any time from the link in the footer.</p></div>
    </div>
    <div class="px-6 pt-4 pb-[22px] max-[600px]:px-[18px]">
      <fieldset class="m-0 rounded-[14px] border border-[#e7e5e4] p-0">
        <legend class="sr-only">Cookie categories</legend>
        <div class="flex items-start gap-4 px-4 py-3.5">
          <div class="min-w-0 flex-1"><label class="flex cursor-pointer items-center gap-2 font-semibold" for="mo-ck-necessary">Necessary <span class="rounded-full bg-[#f5f5f4] px-2 py-px text-[.7rem] font-semibold text-[#44403c]">Always on</span></label><p class="mt-0.5 mb-0 text-[.85rem] leading-[1.45] text-[#57534e]" id="mo-ck-necessary-desc">Keep you signed in, remember your basket and protect the checkout.</p></div>
          <input class="mt-0.5 h-6 w-10 flex-none cursor-pointer appearance-none rounded-full bg-[#78716c] bg-[radial-gradient(circle,#fff_7.5px,transparent_8px)] bg-[length:24px_24px] bg-left bg-no-repeat transition-[background-color,background-position] duration-200 checked:bg-[#c2410c] checked:bg-right focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-55 motion-reduce:transition-none" type="checkbox" role="switch" id="mo-ck-necessary" aria-describedby="mo-ck-necessary-desc" checked disabled>
        </div>
        <div class="flex items-start gap-4 px-4 py-3.5 border-t border-[#e7e5e4]">
          <div class="min-w-0 flex-1"><label class="flex cursor-pointer items-center gap-2 font-semibold" for="mo-ck-analytics">Analytics</label><p class="mt-0.5 mb-0 text-[.85rem] leading-[1.45] text-[#57534e]" id="mo-ck-analytics-desc">Count visits and see which pages are read, so we can fix what is slow or confusing.</p></div>
          <input class="mt-0.5 h-6 w-10 flex-none cursor-pointer appearance-none rounded-full bg-[#78716c] bg-[radial-gradient(circle,#fff_7.5px,transparent_8px)] bg-[length:24px_24px] bg-left bg-no-repeat transition-[background-color,background-position] duration-200 checked:bg-[#c2410c] checked:bg-right focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-55 motion-reduce:transition-none" type="checkbox" role="switch" id="mo-ck-analytics" name="analytics" aria-describedby="mo-ck-analytics-desc">
        </div>
        <div class="flex items-start gap-4 px-4 py-3.5 border-t border-[#e7e5e4]">
          <div class="min-w-0 flex-1"><label class="flex cursor-pointer items-center gap-2 font-semibold" for="mo-ck-marketing">Marketing</label><p class="mt-0.5 mb-0 text-[.85rem] leading-[1.45] text-[#57534e]" id="mo-ck-marketing-desc">Show our ads on other sites and measure whether they lead to an order.</p></div>
          <input class="mt-0.5 h-6 w-10 flex-none cursor-pointer appearance-none rounded-full bg-[#78716c] bg-[radial-gradient(circle,#fff_7.5px,transparent_8px)] bg-[length:24px_24px] bg-left bg-no-repeat transition-[background-color,background-position] duration-200 checked:bg-[#c2410c] checked:bg-right focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-55 motion-reduce:transition-none" type="checkbox" role="switch" id="mo-ck-marketing" name="marketing" aria-describedby="mo-ck-marketing-desc">
        </div>
      </fieldset>
    </div>
    <div class="m-0 flex flex-wrap items-center justify-end gap-2.5 border-t border-[#e7e5e4] bg-[#fafaf9] px-6 py-3.5 max-[600px]:px-[18px]">
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#d6d3d1] bg-white text-[#1c1917] hover:border-[#a8a29e] hover:bg-[#f5f5f4] max-[600px]:flex-auto" value="reject">Reject all</button>
      <button class="inline-flex min-h-[42px] cursor-pointer items-center justify-center gap-2 rounded-[10px] border px-4 py-[9px] text-[.9rem] leading-[1.2] font-semibold whitespace-nowrap no-underline transition-[background-color,border-color,box-shadow] duration-150 focus-visible:outline-2 focus-visible:outline-[#c2410c] focus-visible:outline-offset-2 motion-reduce:transition-none border-[#c2410c] bg-[#c2410c] text-white shadow-[0_6px_16px_-8px_rgba(234,88,12,.8)] hover:border-[#9a3412] hover:bg-[#9a3412] max-[600px]:flex-auto" value="save">Save choices</button>
    </div>
  </form>
</dialog>
CSS
/* Modern CSS modals: native <dialog> opened by invoker commands (commandfor + command="show-modal"), closed by command="close"
   or <form method="dialog">, light dismiss via closedby. Enter AND exit animations are utilities: starting:[&:modal]: is
   @starting-style (keyed to :modal, so a static copy with [open] renders in its final state) and transition-discrete is
   transition-behavior:allow-discrete on display and overlay. Only the page-level rules below cannot be utilities. */
html { scrollbar-gutter: stable; }             /* no sideways jump when the scroll lock hides the scrollbar */
html:has(dialog:modal) { overflow: hidden; }   /* scroll lock while a modal dialog is open */
JavaScript
// Everything above works without this script in browsers with invoker commands and closedby.
// It only fills the gaps in older browsers, and does nothing where those features exist.
// Focus goes back to the button that opened a dialog natively when the dialog closes.
if (!('command' in HTMLButtonElement.prototype)) {
  // No invoker commands: wire command="show-modal" / command="close" buttons to the dialog API
  document.addEventListener('click', function (e) {
    var btn = e.target.closest && e.target.closest('button[commandfor]');
    var dialog = btn && document.getElementById(btn.getAttribute('commandfor'));
    if (!dialog) return;
    if (btn.getAttribute('command') === 'show-modal' && !dialog.open) dialog.showModal();
    else if (btn.getAttribute('command') === 'close') dialog.close();
  });
}
if (!('closedBy' in HTMLDialogElement.prototype)) {
  // No closedby: give closedby="any" dialogs their light dismiss (a click outside the box, on the backdrop)
  document.querySelectorAll('dialog[closedby="any"]').forEach(function (dialog) {
    dialog.addEventListener('click', function (e) {
      var r = dialog.getBoundingClientRect();
      if (e.target === dialog && (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom)) dialog.close();
    });
  });
}

About this set

Three modal dialogs that open, close and animate with HTML and CSS alone, for a ceramics studio’s workspace. Zoom and fade is an invite dialog that scales up from 94 percent and fades in, and plays the same animation in reverse when it closes; it holds an email field, a role select, a Send invite button and the three people who already have access. Slide-over panel is a full-height activity feed that slides in from the right and back out, with eight updates grouped by day. Pop-up sheet is a cookie settings card that rises from the bottom with three labelled switches and Reject all and Save choices. Each trigger is a button with command=”show-modal” and commandfor, so no script opens anything, and close buttons use command=”close” or a form with method=”dialog”. The activity panel has closedby=”any”, so a click on the backdrop closes it; the invite and cookie dialogs use closedby=”closerequest”, so only Esc or a button closes them and a stray click cannot throw away typed input or a consent choice. @starting-style and transitions with allow-discrete animate the dialog and its ::backdrop both ways, overscroll-behavior keeps scrolling inside the panel, and html:has(dialog:modal) locks the page. A short script fills in invoker commands and closedby only where a browser lacks them.

What’s included

  • Opened with invoker commands: command="show-modal" and commandfor, no script
  • Light dismiss with closedby="any", or Esc only with closedby="closerequest"
  • Enter and exit animations with @starting-style and allow-discrete, backdrop included
  • overscroll-behavior:contain and a :has() scroll lock with scrollbar-gutter:stable
  • Fallback script that runs only where invoker commands or closedby are missing

Accessibility

Each dialog is a native modal dialog labelled by its heading, and the cookie sheet is also described by its intro sentence. Focus moves into the dialog, the page behind is inert, Esc closes it and the browser returns focus to the button that opened it. Icon-only buttons have labels, the switches are real checkboxes with role="switch" and linked descriptions, unread activity items say Unread in visually hidden text, and every control shows an orange focus outline. All enter and exit animations are switched off under prefers-reduced-motion.

Customise it

Change the --mo-* custom properties on .mo (accent, ink, muted, line, soft, and --mo-dur and --mo-ease for the animation). The Bootstrap version keeps the same native dialog and borrows .btn, .btn-close, .form-control, .form-select, .form-switch and .list-group, tuned through their --bs-* variables; in Tailwind edit the starting:open: and open: utilities and the #c2410c accent.