Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 12.5 4 4 8-9"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
  <symbol id="i-wifi" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8.8a15 15 0 0 1 20 0M5 12.5a10 10 0 0 1 14 0M8.5 16a5 5 0 0 1 7 0M12 19.5h.01"/></symbol>
  <symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4.5 6v5.5c0 4.6 3.1 8.2 7.5 9.5 4.4-1.3 7.5-4.9 7.5-9.5V6z"/><path d="M12 8.5v4M12 15.5h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3z"/><path d="M12 7.8v5M12 16.2h.01"/></symbol>
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="m8 12.3 2.7 2.7 5.5-5.5"/></symbol>
</svg>

<div><span class="lbl">popover="manual" toasts with @starting-style, no JavaScript</span>
<div class="panel">
  <p class="panel-note">Each button opens a toast in the top layer. Open two or three and they stack; the × on each closes it.</p>
  <div class="mc-row">
    <button class="mc-btn mc-btn-dark" type="button" popovertarget="mc-saved" popovertargetaction="show">Save draft</button>
    <button class="mc-btn" type="button" popovertarget="mc-sent" popovertargetaction="show">Send invoice</button>
    <button class="mc-btn" type="button" popovertarget="mc-offline" popovertargetaction="show">Go offline</button>
  </div>
  <div class="mc-toast" id="mc-saved" popover="manual" role="status"><span class="mc-dot" aria-hidden="true"><svg><use href="#i-ok"/></svg></span><p><b>Draft saved</b>Autumn newsletter, 12:41</p><button class="mc-x" type="button" popovertarget="mc-saved" popovertargetaction="hide" aria-label="Dismiss: Draft saved"><svg aria-hidden="true"><use href="#i-x"/></svg></button></div>
  <div class="mc-toast" id="mc-sent" popover="manual" role="status"><span class="mc-dot is-green" aria-hidden="true"><svg><use href="#i-send"/></svg></span><p><b>Invoice INV-2107 sent</b>To [email protected]</p><button class="mc-x" type="button" popovertarget="mc-sent" popovertargetaction="hide" aria-label="Dismiss: Invoice sent"><svg aria-hidden="true"><use href="#i-x"/></svg></button></div>
  <div class="mc-toast" id="mc-offline" popover="manual" role="status"><span class="mc-dot is-amber" aria-hidden="true"><svg><use href="#i-wifi"/></svg></span><p><b>You are offline</b>Changes will sync when you reconnect</p><button class="mc-x" type="button" popovertarget="mc-offline" popovertargetaction="hide" aria-label="Dismiss: You are offline"><svg aria-hidden="true"><use href="#i-x"/></svg></button></div>
</div></div>

<div><span class="lbl">Notice that closes itself: <dialog open> + form method="dialog"</span>
<div class="panel">
  <dialog class="mc-notice" open aria-labelledby="mc-signin-title">
    <span class="mc-dot" aria-hidden="true"><svg><use href="#i-shield"/></svg></span>
    <div>
      <h3 id="mc-signin-title">New sign-in from Lisbon</h3>
      <p>Firefox on Windows signed in to your account at 09:14 today.</p>
      <form method="dialog"><button class="mc-btn mc-btn-dark" value="secure">Secure my account</button><button class="mc-btn" value="ok">This was me</button></form>
    </div>
  </dialog>
  <dialog class="mc-notice" open aria-labelledby="mc-beta-title">
    <span class="mc-dot" aria-hidden="true"><svg><use href="#i-check"/></svg></span>
    <div>
      <h3 id="mc-beta-title">You are in the calendar beta</h3>
      <p>Shared calendars are switched on for your workspace. Tell us what breaks.</p>
      <form method="dialog"><button class="mc-btn mc-btn-dark" value="feedback">Send feedback</button><button class="mc-btn" value="later">Not now</button></form>
    </div>
  </dialog>
</div></div>

<div><span class="lbl">Live error summary with :has() and :user-invalid</span>
<form class="panel mc-form" action="#">
  <p class="panel-note">Type in a field and move on. The summary lists only the fields that are wrong, and turns green when all are right.</p>
  <div class="mc-alert mc-error" aria-live="polite"><svg aria-hidden="true"><use href="#i-error"/></svg><div><p>Please fix the following</p><ul>
    <li data-for="mc-email">Email needs an @ and a domain, like [email protected]</li>
    <li data-for="mc-pass">Password needs at least 12 characters</li>
    <li data-for="mc-seats">Seats must be between 1 and 50</li>
  </ul></div></div>
  <div class="mc-alert mc-ok" aria-live="polite"><svg aria-hidden="true"><use href="#i-check"/></svg><p>Everything looks right. You can create the workspace.</p></div>
  <div class="mc-field"><label for="mc-email">Work email</label><input id="mc-email" type="email" autocomplete="email" required></div>
  <div class="mc-field"><label for="mc-pass">Password</label><input id="mc-pass" type="password" autocomplete="new-password" minlength="12" required aria-describedby="mc-pass-hint"><p class="mc-hint" id="mc-pass-hint">At least 12 characters.</p></div>
  <div class="mc-field"><label for="mc-seats">Seats</label><input id="mc-seats" type="number" min="1" max="50" value="5" required></div>
  <div><button class="mc-btn mc-btn-dark" type="submit">Create workspace</button></div>
</form></div>
CSS
.mc-btn{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 16px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:#0f172a;font:inherit;font-size:.875rem;font-weight:600;cursor:pointer;transition:background-color .15s}
.mc-btn:hover{background:#f1f5f9}
.mc-btn-dark{border-color:#0f172a;background:#0f172a;color:#fff}
.mc-btn-dark:hover{background:#1e293b}
.mc-btn:focus-visible,.mc-x:focus-visible{outline:2px solid #7c3aed;outline-offset:2px}
.mc-row{display:flex;flex-wrap:wrap;gap:10px}
.mc-x{flex:none;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:7px;background:transparent;color:#64748b;cursor:pointer}
.mc-x:hover{background:#f1f5f9;color:#0f172a}
.mc-x svg{width:15px;height:15px}

/* 1. popover="manual" toasts: opened and closed by popovertarget buttons, no script. They live in the top layer,
      stack upwards with the ~ combinator, and animate in and out with @starting-style + allow-discrete. */
.mc-toast{--mc-up:0;position:fixed;inset:auto 16px calc(16px + var(--mc-up) * 84px) auto;width:min(340px,calc(100vw - 32px));height:72px;margin:0;padding:0 10px 0 14px;
  display:flex;align-items:center;gap:12px;overflow:visible;background:#fff;color:#475569;border:1px solid #e2e8f0;border-radius:14px;
  box-shadow:0 18px 36px -18px rgba(15,23,42,.4),0 2px 6px rgba(15,23,42,.06);font-size:.875rem;line-height:1.45;
  opacity:0;translate:0 16px;transition:opacity .25s,translate .25s,bottom .25s,display .25s allow-discrete,overlay .25s allow-discrete}
.mc-toast:not(:popover-open){display:none}
.mc-toast:popover-open{opacity:1;translate:0 0}
@starting-style{.mc-toast:popover-open{opacity:0;translate:0 16px}}
.mc-toast:popover-open~.mc-toast{--mc-up:1}
.mc-toast:popover-open~.mc-toast:popover-open~.mc-toast{--mc-up:2}
.mc-dot{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:#ede9fe;color:#6d28d9}
.mc-dot svg{width:18px;height:18px}
.mc-dot.is-green{background:#dcfce7;color:#15803d}
.mc-dot.is-amber{background:#fef3c7;color:#b45309}
.mc-toast p{flex:1;min-width:0;margin:0}
.mc-toast .mc-x{margin-left:auto}
.mc-toast b{display:block;color:#0f172a;font-size:.9375rem}

/* 2. A notice that closes itself: a non-modal <dialog open> with <form method="dialog">.
      The exit is animated by transitioning display with allow-discrete. */
.mc-notice{position:static;display:flex;gap:14px;width:100%;margin:0;padding:18px;background:#faf5ff;color:#4c1d95;border:1px solid #ddd6fe;border-radius:14px;
  opacity:1;translate:0 0;transition:opacity .25s,translate .25s,display .25s allow-discrete}
.mc-notice:not([open]){display:none;opacity:0;translate:0 -8px}
@starting-style{.mc-notice[open]{opacity:0;translate:0 -8px}}
.mc-notice h3{margin:0 0 2px;font-size:1rem;color:#2e1065}
.mc-notice p{margin:0;font-size:.9375rem}
.mc-notice form{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.mc-notice .mc-dot{width:36px;height:36px}
.mc-notice .mc-btn{height:36px;border-color:#c4b5fd;color:#4c1d95}
.mc-notice .mc-btn-dark{border-color:#6d28d9;background:#6d28d9;color:#fff}
.mc-notice .mc-btn-dark:hover{background:#5b21b6}
.mc-notice+.mc-notice{margin-top:12px}

/* 3. Live validation alert with :has() and :user-invalid. It appears only after someone has
      interacted with a field, and lists just the fields that are wrong. */
.mc-form{display:grid;gap:14px}
.mc-field label{display:block;margin:0 0 4px;font-size:.875rem;font-weight:600;color:#1e293b}
.mc-field input{width:100%;height:42px;padding:0 12px;border:1px solid #7c8aa0;border-radius:10px;background:#fff;color:#0f172a;font:inherit;font-size:.9375rem}
.mc-field input:focus-visible{outline:3px solid #c4b5fd;outline-offset:0;border-color:#7c3aed}
.mc-field input:user-invalid{border:2px solid #dc2626}
.mc-field input:user-valid{border-color:#16a34a}
.mc-hint{margin:4px 0 0;font-size:.8125rem;color:#5b6474}
.mc-alert{display:none;gap:12px;padding:14px 16px;border-radius:12px;font-size:.9375rem}
.mc-alert svg{flex:none;width:20px;height:20px;margin-top:1px}
.mc-alert p{margin:0;font-weight:650}
.mc-alert ul{margin:4px 0 0;padding-left:18px}
.mc-alert li{display:none}
.mc-error{background:#fef2f2;border:1px solid #fecaca;color:#991b1b}
.mc-error p{color:#7f1d1d}
.mc-ok{background:#f0fdf4;border:1px solid #bbf7d0;color:#166534}
.mc-form:has(:user-invalid) .mc-error{display:flex}
.mc-form:has(#mc-email:user-invalid) [data-for="mc-email"],
.mc-form:has(#mc-pass:user-invalid) [data-for="mc-pass"],
.mc-form:has(#mc-seats:user-invalid) [data-for="mc-seats"]{display:list-item}
.mc-form:not(:has(:invalid)) .mc-ok{display:flex}

@media (prefers-reduced-motion:reduce){.mc-toast,.mc-notice{transition:none}.mc-btn{transition:none}}

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="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 12.5 4 4 8-9"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
  <symbol id="i-wifi" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8.8a15 15 0 0 1 20 0M5 12.5a10 10 0 0 1 14 0M8.5 16a5 5 0 0 1 7 0M12 19.5h.01"/></symbol>
  <symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4.5 6v5.5c0 4.6 3.1 8.2 7.5 9.5 4.4-1.3 7.5-4.9 7.5-9.5V6z"/><path d="M12 8.5v4M12 15.5h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3z"/><path d="M12 7.8v5M12 16.2h.01"/></symbol>
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="m8 12.3 2.7 2.7 5.5-5.5"/></symbol>
</svg>

<div><span class="lbl">popover="manual" toasts with @starting-style, no JavaScript</span>
<div class="panel">
  <p class="panel-note">Each button opens a toast in the top layer. Open two or three and they stack; the × on each closes it.</p>
  <div class="mc-row">
    <button class="btn btn-dark mc-btn" type="button" popovertarget="mc-saved" popovertargetaction="show">Save draft</button>
    <button class="btn btn-light mc-btn" type="button" popovertarget="mc-sent" popovertargetaction="show">Send invoice</button>
    <button class="btn btn-light mc-btn" type="button" popovertarget="mc-offline" popovertargetaction="show">Go offline</button>
  </div>
  <div class="toast mc-toast" id="mc-saved" popover="manual" role="status"><div class="toast-body"><span class="mc-dot" aria-hidden="true"><svg><use href="#i-ok"/></svg></span><p><b>Draft saved</b>Autumn newsletter, 12:41</p><button type="button" class="btn-close" popovertarget="mc-saved" popovertargetaction="hide" aria-label="Dismiss: Draft saved"></button></div></div>
  <div class="toast mc-toast" id="mc-sent" popover="manual" role="status"><div class="toast-body"><span class="mc-dot is-green" aria-hidden="true"><svg><use href="#i-send"/></svg></span><p><b>Invoice INV-2107 sent</b>To [email protected]</p><button type="button" class="btn-close" popovertarget="mc-sent" popovertargetaction="hide" aria-label="Dismiss: Invoice sent"></button></div></div>
  <div class="toast mc-toast" id="mc-offline" popover="manual" role="status"><div class="toast-body"><span class="mc-dot is-amber" aria-hidden="true"><svg><use href="#i-wifi"/></svg></span><p><b>You are offline</b>Changes will sync when you reconnect</p><button type="button" class="btn-close" popovertarget="mc-offline" popovertargetaction="hide" aria-label="Dismiss: You are offline"></button></div></div>
</div></div>

<div><span class="lbl">Notice that closes itself: <dialog open> + form method="dialog"</span>
<div class="panel">
  <dialog class="alert mc-notice" open aria-labelledby="mc-signin-title">
    <span class="mc-dot" aria-hidden="true"><svg><use href="#i-shield"/></svg></span>
    <div>
      <h3 class="alert-heading" id="mc-signin-title">New sign-in from Lisbon</h3>
      <p>Firefox on Windows signed in to your account at 09:14 today.</p>
      <form method="dialog"><button class="btn btn-dark mc-btn" value="secure">Secure my account</button><button class="btn btn-light mc-btn" value="ok">This was me</button></form>
    </div>
  </dialog>
  <dialog class="alert mc-notice" open aria-labelledby="mc-beta-title">
    <span class="mc-dot" aria-hidden="true"><svg><use href="#i-check"/></svg></span>
    <div>
      <h3 class="alert-heading" id="mc-beta-title">You are in the calendar beta</h3>
      <p>Shared calendars are switched on for your workspace. Tell us what breaks.</p>
      <form method="dialog"><button class="btn btn-dark mc-btn" value="feedback">Send feedback</button><button class="btn btn-light mc-btn" value="later">Not now</button></form>
    </div>
  </dialog>
</div></div>

<div><span class="lbl">Live error summary with :has() and :user-invalid</span>
<form class="panel mc-form" action="#">
  <p class="panel-note">Type in a field and move on. The summary lists only the fields that are wrong, and turns green when all are right.</p>
  <div class="alert alert-danger mc-alert" aria-live="polite" data-summary="error"><svg aria-hidden="true"><use href="#i-error"/></svg><div><p>Please fix the following</p><ul>
    <li data-for="mc-email">Email needs an @ and a domain, like [email protected]</li>
    <li data-for="mc-pass">Password needs at least 12 characters</li>
    <li data-for="mc-seats">Seats must be between 1 and 50</li>
  </ul></div></div>
  <div class="alert alert-success mc-alert" aria-live="polite" data-summary="ok"><svg aria-hidden="true"><use href="#i-check"/></svg><p>Everything looks right. You can create the workspace.</p></div>
  <div><label class="form-label" for="mc-email">Work email</label><input class="form-control" id="mc-email" type="email" autocomplete="email" required></div>
  <div><label class="form-label" for="mc-pass">Password</label><input class="form-control" id="mc-pass" type="password" autocomplete="new-password" minlength="12" required aria-describedby="mc-pass-hint"><p class="form-text" id="mc-pass-hint">At least 12 characters.</p></div>
  <div><label class="form-label" for="mc-seats">Seats</label><input class="form-control" id="mc-seats" type="number" min="1" max="50" value="5" required></div>
  <div><button class="btn btn-dark mc-btn" type="submit">Create workspace</button></div>
</form></div>
CSS
/* Bootstrap markup (.toast, .alert, .btn, .btn-close, .form-control) driven by the popover attribute, <dialog> and :has() instead of Bootstrap's JavaScript. */
.mc-btn{--bs-btn-padding-x:16px;--bs-btn-font-size:.875rem;--bs-btn-font-weight:600;--bs-btn-border-radius:10px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px #7c3aed;display:inline-flex;align-items:center;height:40px}
.mc-btn.btn-light{--bs-btn-bg:#fff;--bs-btn-border-color:#cbd5e1;--bs-btn-color:#0f172a;--bs-btn-hover-bg:#f1f5f9;--bs-btn-hover-border-color:#cbd5e1;--bs-btn-active-bg:#e2e8f0}
.mc-btn.btn-dark{--bs-btn-bg:#0f172a;--bs-btn-border-color:#0f172a;--bs-btn-hover-bg:#1e293b;--bs-btn-hover-border-color:#1e293b}
.mc-row{display:flex;flex-wrap:wrap;gap:10px}
.mc-dot{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:#ede9fe;color:#6d28d9}
.mc-dot svg{width:18px;height:18px}
.mc-dot.is-green{background:#dcfce7;color:#15803d}
.mc-dot.is-amber{background:#fef3c7;color:#b45309}

/* 1. popover="manual" toasts */
.mc-toast{--bs-toast-max-width:none;--bs-toast-font-size:.875rem;--bs-toast-color:#475569;--bs-toast-bg:#fff;--bs-toast-border-color:#e2e8f0;--bs-toast-border-radius:14px;
  --bs-toast-box-shadow:0 18px 36px -18px rgba(15,23,42,.4),0 2px 6px rgba(15,23,42,.06);--mc-up:0;
  position:fixed;inset:auto 16px calc(16px + var(--mc-up) * 84px) auto;width:min(340px,calc(100vw - 32px));height:72px;margin:0;padding:0;overflow:visible;line-height:1.45;
  opacity:0;translate:0 16px;transition:opacity .25s,translate .25s,bottom .25s,display .25s allow-discrete,overlay .25s allow-discrete}
.mc-toast:not(:popover-open){display:none}
.mc-toast:popover-open{display:block;opacity:1;translate:0 0}
@starting-style{.mc-toast:popover-open{opacity:0;translate:0 16px}}
.mc-toast:popover-open~.mc-toast{--mc-up:1}
.mc-toast:popover-open~.mc-toast:popover-open~.mc-toast{--mc-up:2}
.mc-toast .toast-body{display:flex;align-items:center;gap:12px;height:100%;padding:0 10px 0 14px}
.mc-toast p{flex:1;min-width:0;margin:0}
.mc-toast b{display:block;color:#0f172a;font-size:.9375rem}
.mc-toast .btn-close{--bs-btn-close-opacity:.6;--bs-btn-close-hover-opacity:1;--bs-btn-close-focus-shadow:0 0 0 2px #7c3aed;flex:none;width:28px;height:28px;padding:0;border-radius:7px;background-size:10px}
.mc-toast .btn-close:hover{background-color:#f1f5f9}

/* 2. Non-modal <dialog open class="alert"> closed by <form method="dialog"> */
.mc-notice{--bs-alert-bg:#faf5ff;--bs-alert-color:#4c1d95;--bs-alert-border-color:#ddd6fe;--bs-alert-border-radius:14px;--bs-alert-padding-x:18px;--bs-alert-padding-y:18px;--bs-alert-margin-bottom:0;
  position:static;display:flex;gap:14px;width:100%;margin:0;opacity:1;translate:0 0;transition:opacity .25s,translate .25s,display .25s allow-discrete}
.mc-notice:not([open]){display:none;opacity:0;translate:0 -8px}
@starting-style{.mc-notice[open]{opacity:0;translate:0 -8px}}
.mc-notice .alert-heading{margin:0 0 2px;font-size:1rem;font-weight:700;color:#2e1065}
.mc-notice p{margin:0;font-size:.9375rem}
.mc-notice form{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.mc-notice .mc-dot{width:36px;height:36px}
.mc-notice .mc-btn{height:36px}
.mc-notice .btn-light{--bs-btn-border-color:#c4b5fd;--bs-btn-color:#4c1d95;--bs-btn-hover-border-color:#c4b5fd;--bs-btn-hover-color:#4c1d95}
.mc-notice .btn-dark{--bs-btn-bg:#6d28d9;--bs-btn-border-color:#6d28d9;--bs-btn-hover-bg:#5b21b6;--bs-btn-hover-border-color:#5b21b6;--bs-btn-active-bg:#5b21b6}
.mc-notice+.mc-notice{margin-top:12px}

/* 3. Live error summary with :has() and :user-invalid */
.mc-form{display:grid;gap:14px}
.mc-form .form-label{margin:0 0 4px;font-size:.875rem;font-weight:600;color:#1e293b}
.mc-form .form-control{height:42px;padding:0 12px;border-color:#7c8aa0;border-radius:10px;font-size:.9375rem;color:#0f172a}
.mc-form .form-control:focus{border-color:#7c3aed;box-shadow:0 0 0 3px #c4b5fd}
.mc-form .form-control:user-invalid{border:2px solid #dc2626}
.mc-form .form-control:user-valid{border-color:#16a34a}
.mc-form .form-text{margin:4px 0 0;font-size:.8125rem;color:#5b6474}
.mc-alert{--bs-alert-margin-bottom:0;--bs-alert-padding-x:16px;--bs-alert-padding-y:14px;--bs-alert-border-radius:12px;display:none;gap:12px;font-size:.9375rem}
.mc-alert svg{flex:none;width:20px;height:20px;margin-top:1px}
.mc-alert p{margin:0;font-weight:650}
.mc-alert ul{margin:4px 0 0;padding-left:18px}
.mc-alert li{display:none}
.mc-alert.alert-danger{--bs-alert-bg:#fef2f2;--bs-alert-border-color:#fecaca;--bs-alert-color:#991b1b}
.mc-alert.alert-danger p{color:#7f1d1d}
.mc-alert.alert-success{--bs-alert-bg:#f0fdf4;--bs-alert-border-color:#bbf7d0;--bs-alert-color:#166534}
.mc-form:has(:user-invalid) .alert-danger{display:flex}
.mc-form:has(#mc-email:user-invalid) [data-for="mc-email"],
.mc-form:has(#mc-pass:user-invalid) [data-for="mc-pass"],
.mc-form:has(#mc-seats:user-invalid) [data-for="mc-seats"]{display:list-item}
.mc-form:not(:has(:invalid)) .alert-success{display:flex}

@media (prefers-reduced-motion:reduce){.mc-toast,.mc-notice{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 12.5 4 4 8-9"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
  <symbol id="i-wifi" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8.8a15 15 0 0 1 20 0M5 12.5a10 10 0 0 1 14 0M8.5 16a5 5 0 0 1 7 0M12 19.5h.01"/></symbol>
  <symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4.5 6v5.5c0 4.6 3.1 8.2 7.5 9.5 4.4-1.3 7.5-4.9 7.5-9.5V6z"/><path d="M12 8.5v4M12 15.5h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3z"/><path d="M12 7.8v5M12 16.2h.01"/></symbol>
  <symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="m8 12.3 2.7 2.7 5.5-5.5"/></symbol>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">popover="manual" toasts with @starting-style, no JavaScript</span>
<div class="rounded-2xl border border-[#e2e8f0] bg-white p-6 shadow-[0_1px_2px_rgba(15,23,42,.05)] max-[600px]:p-[18px]">
  <p class="mb-4 text-sm text-[#5b6474]">Each button opens a toast in the top layer. Open two or three and they stack; the × on each closes it.</p>
  <div class="flex flex-wrap gap-2.5">
    <button class="inline-flex h-10 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#0f172a] bg-[#0f172a] text-white hover:bg-[#1e293b]" type="button" popovertarget="mc-saved" popovertargetaction="show">Save draft</button>
    <button class="inline-flex h-10 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:bg-[#f1f5f9]" type="button" popovertarget="mc-sent" popovertargetaction="show">Send invoice</button>
    <button class="inline-flex h-10 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#cbd5e1] bg-white text-[#0f172a] hover:bg-[#f1f5f9]" type="button" popovertarget="mc-offline" popovertargetaction="show">Go offline</button>
  </div>
  <div class="fixed inset-auto right-4 bottom-4 m-0 h-[72px] w-[min(340px,calc(100vw-32px))] items-center gap-3 overflow-visible rounded-[14px] border border-[#e2e8f0] bg-white py-0 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#475569] shadow-[0_18px_36px_-18px_rgba(15,23,42,.4),0_2px_6px_rgba(15,23,42,.06)] translate-y-4 opacity-0 transition-[opacity,translate,bottom,display,overlay] duration-250 transition-discrete not-open:hidden open:flex open:translate-y-0 open:opacity-100 starting:open:translate-y-4 starting:open:opacity-0 [:popover-open~&]:bottom-[100px] [:popover-open~:popover-open~&]:bottom-[184px] motion-reduce:transition-none" id="mc-saved" popover="manual" role="status"><span class="grid size-8 flex-none place-items-center rounded-full bg-[#ede9fe] text-[#6d28d9]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-ok"/></svg></span><p class="min-w-0 flex-1"><b class="block text-[.9375rem] text-[#0f172a]">Draft saved</b>Autumn newsletter, 12:41</p><button class="grid size-7 flex-none cursor-pointer place-items-center rounded-[7px] text-[#64748b] hover:bg-[#f1f5f9] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed]" type="button" popovertarget="mc-saved" popovertargetaction="hide" aria-label="Dismiss: Draft saved"><svg class="size-[15px]" aria-hidden="true"><use href="#i-x"/></svg></button></div>
  <div class="fixed inset-auto right-4 bottom-4 m-0 h-[72px] w-[min(340px,calc(100vw-32px))] items-center gap-3 overflow-visible rounded-[14px] border border-[#e2e8f0] bg-white py-0 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#475569] shadow-[0_18px_36px_-18px_rgba(15,23,42,.4),0_2px_6px_rgba(15,23,42,.06)] translate-y-4 opacity-0 transition-[opacity,translate,bottom,display,overlay] duration-250 transition-discrete not-open:hidden open:flex open:translate-y-0 open:opacity-100 starting:open:translate-y-4 starting:open:opacity-0 [:popover-open~&]:bottom-[100px] [:popover-open~:popover-open~&]:bottom-[184px] motion-reduce:transition-none" id="mc-sent" popover="manual" role="status"><span class="grid size-8 flex-none place-items-center rounded-full bg-[#dcfce7] text-[#15803d]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-send"/></svg></span><p class="min-w-0 flex-1"><b class="block text-[.9375rem] text-[#0f172a]">Invoice INV-2107 sent</b>To [email protected]</p><button class="grid size-7 flex-none cursor-pointer place-items-center rounded-[7px] text-[#64748b] hover:bg-[#f1f5f9] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed]" type="button" popovertarget="mc-sent" popovertargetaction="hide" aria-label="Dismiss: Invoice sent"><svg class="size-[15px]" aria-hidden="true"><use href="#i-x"/></svg></button></div>
  <div class="fixed inset-auto right-4 bottom-4 m-0 h-[72px] w-[min(340px,calc(100vw-32px))] items-center gap-3 overflow-visible rounded-[14px] border border-[#e2e8f0] bg-white py-0 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#475569] shadow-[0_18px_36px_-18px_rgba(15,23,42,.4),0_2px_6px_rgba(15,23,42,.06)] translate-y-4 opacity-0 transition-[opacity,translate,bottom,display,overlay] duration-250 transition-discrete not-open:hidden open:flex open:translate-y-0 open:opacity-100 starting:open:translate-y-4 starting:open:opacity-0 [:popover-open~&]:bottom-[100px] [:popover-open~:popover-open~&]:bottom-[184px] motion-reduce:transition-none" id="mc-offline" popover="manual" role="status"><span class="grid size-8 flex-none place-items-center rounded-full bg-[#fef3c7] text-[#b45309]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-wifi"/></svg></span><p class="min-w-0 flex-1"><b class="block text-[.9375rem] text-[#0f172a]">You are offline</b>Changes will sync when you reconnect</p><button class="grid size-7 flex-none cursor-pointer place-items-center rounded-[7px] text-[#64748b] hover:bg-[#f1f5f9] hover:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed]" type="button" popovertarget="mc-offline" popovertargetaction="hide" aria-label="Dismiss: You are offline"><svg class="size-[15px]" aria-hidden="true"><use href="#i-x"/></svg></button></div>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Notice that closes itself: <dialog open> + form method="dialog"</span>
<div class="rounded-2xl border border-[#e2e8f0] bg-white p-6 shadow-[0_1px_2px_rgba(15,23,42,.05)] max-[600px]:p-[18px]">
  <dialog class="static m-0 flex w-full gap-3.5 rounded-[14px] border border-[#ddd6fe] bg-[#faf5ff] p-[18px] text-[#4c1d95] -translate-y-2 opacity-0 transition-[opacity,translate,display] duration-250 transition-discrete not-open:hidden open:translate-y-0 open:opacity-100 starting:open:-translate-y-2 starting:open:opacity-0 motion-reduce:transition-none" open aria-labelledby="mc-signin-title">
    <span class="grid size-9 flex-none place-items-center rounded-full bg-[#ede9fe] text-[#6d28d9]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-shield"/></svg></span>
    <div>
      <h3 class="mb-0.5 text-base font-bold text-[#2e1065]" id="mc-signin-title">New sign-in from Lisbon</h3>
      <p class="text-[.9375rem]">Firefox on Windows signed in to your account at 09:14 today.</p>
      <form class="mt-3 flex flex-wrap gap-2" method="dialog"><button class="inline-flex h-9 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#6d28d9] bg-[#6d28d9] text-white hover:bg-[#5b21b6]" value="secure">Secure my account</button><button class="inline-flex h-9 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#c4b5fd] bg-white text-[#4c1d95] hover:bg-[#f1f5f9]" value="ok">This was me</button></form>
    </div>
  </dialog>
  <dialog class="static m-0 flex w-full gap-3.5 rounded-[14px] border border-[#ddd6fe] bg-[#faf5ff] p-[18px] text-[#4c1d95] -translate-y-2 opacity-0 transition-[opacity,translate,display] duration-250 transition-discrete not-open:hidden open:translate-y-0 open:opacity-100 starting:open:-translate-y-2 starting:open:opacity-0 motion-reduce:transition-none mt-3" open aria-labelledby="mc-beta-title">
    <span class="grid size-9 flex-none place-items-center rounded-full bg-[#ede9fe] text-[#6d28d9]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-check"/></svg></span>
    <div>
      <h3 class="mb-0.5 text-base font-bold text-[#2e1065]" id="mc-beta-title">You are in the calendar beta</h3>
      <p class="text-[.9375rem]">Shared calendars are switched on for your workspace. Tell us what breaks.</p>
      <form class="mt-3 flex flex-wrap gap-2" method="dialog"><button class="inline-flex h-9 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#6d28d9] bg-[#6d28d9] text-white hover:bg-[#5b21b6]" value="feedback">Send feedback</button><button class="inline-flex h-9 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#c4b5fd] bg-white text-[#4c1d95] hover:bg-[#f1f5f9]" value="later">Not now</button></form>
    </div>
  </dialog>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Live error summary with :has() and :user-invalid</span>
<form class="grid gap-3.5 rounded-2xl border border-[#e2e8f0] bg-white p-6 shadow-[0_1px_2px_rgba(15,23,42,.05)] max-[600px]:p-[18px]" action="#">
  <p class="text-sm text-[#5b6474]">Type in a field and move on. The summary lists only the fields that are wrong, and turns green when all are right.</p>
  <div class="hidden gap-3 rounded-xl border border-[#fecaca] bg-[#fef2f2] px-4 py-3.5 text-[.9375rem] text-[#991b1b] [form:has(:user-invalid)_&]:flex" aria-live="polite" data-summary="error"><svg class="mt-px size-5 flex-none" aria-hidden="true"><use href="#i-error"/></svg><div><p class="font-[650] text-[#7f1d1d]">Please fix the following</p><ul class="mt-1 list-disc pl-[18px]">
    <li class="hidden [form:has(#mc-email:user-invalid)_&]:list-item" data-for="mc-email">Email needs an @ and a domain, like [email protected]</li>
    <li class="hidden [form:has(#mc-pass:user-invalid)_&]:list-item" data-for="mc-pass">Password needs at least 12 characters</li>
    <li class="hidden [form:has(#mc-seats:user-invalid)_&]:list-item" data-for="mc-seats">Seats must be between 1 and 50</li>
  </ul></div></div>
  <div class="hidden gap-3 rounded-xl border border-[#bbf7d0] bg-[#f0fdf4] px-4 py-3.5 text-[.9375rem] text-[#166534] [form:not(:has(:invalid))_&]:flex" aria-live="polite" data-summary="ok"><svg class="mt-px size-5 flex-none" aria-hidden="true"><use href="#i-check"/></svg><p class="font-[650]">Everything looks right. You can create the workspace.</p></div>
  <div><label class="mb-1 block text-sm font-semibold text-[#1e293b]" for="mc-email">Work email</label><input class="h-[42px] w-full rounded-[10px] border border-[#7c8aa0] bg-white px-3 text-[.9375rem] text-[#0f172a] focus-visible:border-[#7c3aed] focus-visible:outline-3 focus-visible:outline-[#c4b5fd] user-invalid:border-2 user-invalid:border-[#dc2626] user-valid:border-[#16a34a]" id="mc-email" type="email" autocomplete="email" required></div>
  <div><label class="mb-1 block text-sm font-semibold text-[#1e293b]" for="mc-pass">Password</label><input class="h-[42px] w-full rounded-[10px] border border-[#7c8aa0] bg-white px-3 text-[.9375rem] text-[#0f172a] focus-visible:border-[#7c3aed] focus-visible:outline-3 focus-visible:outline-[#c4b5fd] user-invalid:border-2 user-invalid:border-[#dc2626] user-valid:border-[#16a34a]" id="mc-pass" type="password" autocomplete="new-password" minlength="12" required aria-describedby="mc-pass-hint"><p class="mt-1 text-[.8125rem] text-[#5b6474]" id="mc-pass-hint">At least 12 characters.</p></div>
  <div><label class="mb-1 block text-sm font-semibold text-[#1e293b]" for="mc-seats">Seats</label><input class="h-[42px] w-full rounded-[10px] border border-[#7c8aa0] bg-white px-3 text-[.9375rem] text-[#0f172a] focus-visible:border-[#7c3aed] focus-visible:outline-3 focus-visible:outline-[#c4b5fd] user-invalid:border-2 user-invalid:border-[#dc2626] user-valid:border-[#16a34a]" id="mc-seats" type="number" min="1" max="50" value="5" required></div>
  <div><button class="inline-flex h-10 cursor-pointer items-center gap-2 rounded-[10px] border px-4 text-sm font-semibold transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7c3aed] motion-reduce:transition-none border-[#0f172a] bg-[#0f172a] text-white hover:bg-[#1e293b]" type="submit">Create workspace</button></div>
</form></div>

About this set

Three patterns that usually need JavaScript, done with current HTML and CSS instead. The popover toasts are elements with popover=”manual” opened and closed by buttons with popovertarget, so they sit in the top layer above everything, with no z-index battles and no script. They fade and slide in and out with @starting-style and transition-behavior: allow-discrete, and they stack upwards because the general sibling combinator moves each toast up when an earlier one is open. They do not close themselves, since that still needs a timer. The self-closing notices are non-modal dialog elements that start open; their buttons sit in a form with method=”dialog”, which closes the dialog natively, and a display transition animates the exit. The live error summary uses :has() with :user-invalid: the red summary appears only after someone has edited a field, lists only the fields that are wrong, and a green message replaces it when nothing is invalid. The browser’s own validation still runs on submit. The Bootstrap version applies the same techniques to .toast, .alert and .form-control markup without Bootstrap’s JavaScript.

What’s included

  • popover="manual" toasts with no JavaScript
  • Enter and exit animation with @starting-style and allow-discrete
  • Toasts stack with the ~ sibling combinator
  • Notices closed by form method="dialog"
  • Error summary from :has() and :user-invalid

Accessibility

Toast and notice buttons are real buttons, browsers expose each opener's expanded state from popovertarget, and each toast has role="status"; screen reader announcement of a newly shown popover varies by browser, so use the scripted stack in set 5 for background events. Dialog notices are labelled by their headings. The error summary is a visual aid in an aria-live region, and native validation messages still fire on submit. All motion is removed under prefers-reduced-motion.

Customise it

The toast corner is the inset on .mc-toast and the stacking step is the 84px in its bottom calc. Notice colours are on .mc-notice, and the summary rules pair each field id with a data-for list item, so add a field by adding one selector and one li. In Bootstrap the same rules sit on .mc-toast, .mc-notice and .mc-alert; in Tailwind edit the open:, starting: and arbitrary [form:has(...)_&] variants.