Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-info" 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="M12 11v5.5M12 7.6h.01"/></symbol>
  <symbol id="i-ok" 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>
  <symbol id="i-warn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 4.2 2.9 17.3a2 2 0 0 0 1.7 3h14.8a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4.2M12 16.9h.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-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>
</svg>

<div><span class="lbl">Soft with icon, title and dismiss</span>
<div class="al-stack">
  <div class="al al-info" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg>
    <div class="al-body"><p class="al-title">Passkeys are here</p><p class="al-text">Sign in to Harbor with Face ID or a security key instead of a password. <a href="#">Set up a passkey</a></p></div>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Passkeys are here"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-success" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg>
    <div class="al-body"><p class="al-title">Payment received</p><p class="al-text">€1,240.00 from Kestrel Studio has been applied to invoice INV-2093.</p></div>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Payment received"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-warning" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg>
    <div class="al-body"><p class="al-title">Storage almost full</p><p class="al-text">You have used 9.2 GB of 10 GB. New uploads pause at the limit. <a href="#">Manage storage</a></p></div>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Storage almost full"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-danger" role="alert" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg>
    <div class="al-body"><p class="al-title">Card declined</p><p class="al-text">Your Visa ending 4417 was declined on 24 September. <a href="#">Update payment details</a> to keep your workspace active.</p></div>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Card declined"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
</div></div>

<div><span class="lbl">Accent bar, one line</span>
<div class="al-stack">
  <div class="al al-info al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg><p class="al-text"><span class="sr-only">Information: </span>Harbor is read-only from <strong>02:00 to 02:10 UTC</strong> on Sunday.</p></div>
  <div class="al al-success al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg><p class="al-text"><span class="sr-only">Success: </span><strong>48 contacts</strong> imported from customers-sept.csv.</p></div>
  <div class="al al-warning al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg><p class="al-text"><span class="sr-only">Warning: </span>Your API key expires in <strong>5 days</strong>. <a href="#">Rotate key</a></p></div>
  <div class="al al-danger al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg><p class="al-text"><span class="sr-only">Error: </span>The Stripe webhook did not respond. Last attempt at <strong>09:41</strong>.</p></div>
</div></div>

<div><span class="lbl">Solid, high emphasis</span>
<div class="al-stack">
  <div class="al al-info al-solid" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg>
    <p class="al-text al-body"><span class="sr-only">Information: </span>Version 4.2 is rolling out to your workspace this week. <a href="#">What's new</a></p>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Version 4.2"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-success al-solid" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg>
    <p class="al-text al-body"><span class="sr-only">Success: </span>harbor-studio.com is verified and now sends from your domain.</p>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Domain verified"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-warning al-solid" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg>
    <p class="al-text al-body"><span class="sr-only">Warning: </span>Two-factor authentication is off for 3 admins. <a href="#">Review admins</a></p>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Two-factor warning"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="al al-danger al-solid" data-alert>
    <svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg>
    <p class="al-text al-body"><span class="sr-only">Error: </span>Sync stopped. 12 files were not uploaded. <a href="#">Retry sync</a></p>
    <button class="al-close" type="button" data-dismiss aria-label="Dismiss: Sync stopped"><svg aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
</div></div>
CSS
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.al-stack{display:flex;flex-direction:column;gap:12px}

/* Status palette: every alert reads its colours from these five properties. */
.al{--al-bg:#eff6ff;--al-line:#bfdbfe;--al-icon:#2563eb;--al-title:#1e3a8a;--al-ink:#1e40af}
.al-success{--al-bg:#f0fdf4;--al-line:#bbf7d0;--al-icon:#16a34a;--al-title:#14532d;--al-ink:#166534}
.al-warning{--al-bg:#fffbeb;--al-line:#fde68a;--al-icon:#d97706;--al-title:#713f12;--al-ink:#854d0e}
.al-danger{--al-bg:#fef2f2;--al-line:#fecaca;--al-icon:#dc2626;--al-title:#7f1d1d;--al-ink:#991b1b}

/* 1. Soft: tinted card with an icon, a title and a dismiss button */
.al{display:flex;align-items:flex-start;gap:12px;padding:14px 12px 14px 16px;border:1px solid var(--al-line);border-radius:12px;
  background:var(--al-bg);color:var(--al-ink);font-size:.9375rem;line-height:1.5}
.al-icon{flex:none;width:20px;height:20px;margin-top:2px;color:var(--al-icon)}
.al-body{flex:1;min-width:0}
.al-title{margin:0 0 2px;font-weight:650;color:var(--al-title)}
.al-text{margin:0}
.al a{color:var(--al-title);font-weight:600;text-decoration:underline;text-underline-offset:2px;border-radius:3px}
.al a:hover{text-decoration-thickness:2px}
.al a:focus-visible,.al-close:focus-visible{outline:2px solid var(--al-title);outline-offset:2px}
.al-close{flex:none;display:grid;place-items:center;width:30px;height:30px;margin:-4px 0 -4px 4px;padding:0;border:0;border-radius:8px;
  background:transparent;color:var(--al-ink);cursor:pointer;transition:background-color .15s}
.al-close:hover{background:rgba(15,23,42,.07)}
.al-close svg{width:16px;height:16px}

/* 2. Accent bar: white card, 4px status stripe, one line of text */
.al.al-bar{align-items:center;padding:12px 16px;border:1px solid #e2e8f0;border-left:4px solid var(--al-icon);border-radius:10px;background:#fff;
  color:#334155;box-shadow:0 1px 2px rgba(15,23,42,.05)}
.al-bar .al-icon{margin-top:0}
.al-bar .al-text{flex:1;min-width:0}
.al-bar strong{color:#0f172a;font-weight:650}
.al-bar a{color:#1d4ed8}
.al-bar a:focus-visible{outline-color:#1d4ed8}

/* 3. Solid: filled, for the one message a page must not miss */
.al.al-solid{border:0;background:var(--al-solid);color:#fff;box-shadow:0 10px 24px -14px var(--al-solid)}
.al-solid{--al-solid:#1d4ed8}
.al-solid.al-success{--al-solid:#15803d}
.al-solid.al-danger{--al-solid:#b91c1c}
.al-solid.al-warning{--al-solid:#fbbf24;color:#422006}
.al-solid .al-icon,.al-solid .al-title,.al-solid .al-close,.al-solid a{color:inherit}
.al-solid .al-close:hover{background:rgba(255,255,255,.18)}
.al-solid.al-warning .al-close:hover{background:rgba(66,32,6,.1)}
.al-solid a:focus-visible,.al-solid .al-close:focus-visible{outline-color:currentColor}

@media (prefers-reduced-motion:reduce){.al-close{transition:none}}
JavaScript
// Dismiss: a button with data-dismiss removes its [data-alert]. Focus moves to the next alert's
// button so keyboard users are not sent back to the top of the page.
document.addEventListener('click', function (e) {
  var btn = e.target.closest('[data-dismiss]');
  if (!btn) return;
  var alert = btn.closest('[data-alert]');
  if (!alert) return;
  var sibling = alert.nextElementSibling || alert.previousElementSibling;
  var next = sibling && sibling.querySelector('[data-dismiss], a[href], button');
  var done = function () { alert.remove(); if (next) next.focus(); };
  if (!alert.animate || matchMedia('(prefers-reduced-motion: reduce)').matches) return done();
  alert.animate([{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateY(-4px)' }],
    { duration: 180, easing: 'ease-in' }).onfinish = done;
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-info" 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="M12 11v5.5M12 7.6h.01"/></symbol>
  <symbol id="i-ok" 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>
  <symbol id="i-warn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 4.2 2.9 17.3a2 2 0 0 0 1.7 3h14.8a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4.2M12 16.9h.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>
</svg>

<div><span class="lbl">Soft with icon, title and dismiss</span>
<div class="al-stack">
  <div class="alert alert-info fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg>
    <div class="flex-grow-1"><p class="alert-heading">Passkeys are here</p><p class="mb-0">Sign in to Harbor with Face ID or a security key instead of a password. <a href="#" class="alert-link">Set up a passkey</a></p></div>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Dismiss: Passkeys are here"></button>
  </div>
  <div class="alert alert-success fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg>
    <div class="flex-grow-1"><p class="alert-heading">Payment received</p><p class="mb-0">€1,240.00 from Kestrel Studio has been applied to invoice INV-2093.</p></div>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Dismiss: Payment received"></button>
  </div>
  <div class="alert alert-warning fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg>
    <div class="flex-grow-1"><p class="alert-heading">Storage almost full</p><p class="mb-0">You have used 9.2 GB of 10 GB. New uploads pause at the limit. <a href="#" class="alert-link">Manage storage</a></p></div>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Dismiss: Storage almost full"></button>
  </div>
  <div class="alert alert-danger fade show" role="alert">
    <svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg>
    <div class="flex-grow-1"><p class="alert-heading">Card declined</p><p class="mb-0">Your Visa ending 4417 was declined on 24 September. <a href="#" class="alert-link">Update payment details</a> to keep your workspace active.</p></div>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Dismiss: Card declined"></button>
  </div>
</div></div>

<div><span class="lbl">Accent bar, one line</span>
<div class="al-stack">
  <div class="alert alert-info al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg><p class="mb-0 flex-grow-1"><span class="visually-hidden">Information: </span>Harbor is read-only from <strong>02:00 to 02:10 UTC</strong> on Sunday.</p></div>
  <div class="alert alert-success al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg><p class="mb-0 flex-grow-1"><span class="visually-hidden">Success: </span><strong>48 contacts</strong> imported from customers-sept.csv.</p></div>
  <div class="alert alert-warning al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg><p class="mb-0 flex-grow-1"><span class="visually-hidden">Warning: </span>Your API key expires in <strong>5 days</strong>. <a href="#" class="alert-link">Rotate key</a></p></div>
  <div class="alert alert-danger al-bar"><svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg><p class="mb-0 flex-grow-1"><span class="visually-hidden">Error: </span>The Stripe webhook did not respond. Last attempt at <strong>09:41</strong>.</p></div>
</div></div>

<div><span class="lbl">Solid, high emphasis</span>
<div class="al-stack">
  <div class="alert alert-info al-solid fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-info"/></svg>
    <p class="mb-0 flex-grow-1"><span class="visually-hidden">Information: </span>Version 4.2 is rolling out to your workspace this week. <a href="#" class="alert-link">What's new</a></p>
    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert" aria-label="Dismiss: Version 4.2"></button>
  </div>
  <div class="alert alert-success al-solid fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-ok"/></svg>
    <p class="mb-0 flex-grow-1"><span class="visually-hidden">Success: </span>harbor-studio.com is verified and now sends from your domain.</p>
    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert" aria-label="Dismiss: Domain verified"></button>
  </div>
  <div class="alert alert-warning al-solid fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-warn"/></svg>
    <p class="mb-0 flex-grow-1"><span class="visually-hidden">Warning: </span>Two-factor authentication is off for 3 admins. <a href="#" class="alert-link">Review admins</a></p>
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Dismiss: Two-factor warning"></button>
  </div>
  <div class="alert alert-danger al-solid fade show">
    <svg class="al-icon" aria-hidden="true"><use href="#i-error"/></svg>
    <p class="mb-0 flex-grow-1"><span class="visually-hidden">Error: </span>Sync stopped. 12 files were not uploaded. <a href="#" class="alert-link">Retry sync</a></p>
    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert" aria-label="Dismiss: Sync stopped"></button>
  </div>
</div></div>
CSS
.al-stack{display:flex;flex-direction:column;gap:12px}
/* Bootstrap .alert restyled through its own --bs-alert-* variables. Dismiss uses the Alert plugin (data-bs-dismiss="alert"). */
.alert{--bs-alert-margin-bottom:0;--bs-alert-padding-x:16px;--bs-alert-padding-y:14px;--bs-alert-border-radius:12px;
  display:flex;align-items:flex-start;gap:12px;font-size:.9375rem;line-height:1.5}
.alert-info{--bs-alert-bg:#eff6ff;--bs-alert-border-color:#bfdbfe;--bs-alert-color:#1e40af;--bs-alert-link-color:#1e3a8a;--al-icon:#2563eb}
.alert-success{--bs-alert-bg:#f0fdf4;--bs-alert-border-color:#bbf7d0;--bs-alert-color:#166534;--bs-alert-link-color:#14532d;--al-icon:#16a34a}
.alert-warning{--bs-alert-bg:#fffbeb;--bs-alert-border-color:#fde68a;--bs-alert-color:#854d0e;--bs-alert-link-color:#713f12;--al-icon:#d97706}
.alert-danger{--bs-alert-bg:#fef2f2;--bs-alert-border-color:#fecaca;--bs-alert-color:#991b1b;--bs-alert-link-color:#7f1d1d;--al-icon:#dc2626}
.al-icon{flex:none;width:20px;height:20px;margin-top:2px;color:var(--al-icon)}
.alert-heading{margin:0 0 2px;font-size:1em;font-weight:650;color:var(--bs-alert-link-color)}
.alert .alert-link{font-weight:600;text-decoration:underline;text-underline-offset:2px;border-radius:3px}
.alert .alert-link:hover{text-decoration-thickness:2px}
.alert .alert-link:focus-visible{outline:2px solid var(--bs-alert-link-color);outline-offset:2px}
.alert .btn-close{--bs-btn-close-opacity:.7;--bs-btn-close-hover-opacity:1;--bs-btn-close-focus-opacity:1;
  --bs-btn-close-focus-shadow:0 0 0 2px var(--bs-alert-bg),0 0 0 4px var(--bs-alert-link-color);
  flex:none;width:30px;height:30px;margin:-4px -4px -4px 4px;padding:0;border-radius:8px;background-size:11px}
.alert .btn-close:hover{background-color:rgba(15,23,42,.07)}

/* 2. Accent bar */
.alert.al-bar{--bs-alert-bg:#fff;--bs-alert-border-color:#e2e8f0;--bs-alert-color:#334155;--bs-alert-link-color:#1d4ed8;--bs-alert-padding-y:12px;--bs-alert-border-radius:10px;
  align-items:center;border-left:4px solid var(--al-icon);box-shadow:0 1px 2px rgba(15,23,42,.05)}
.al-bar .al-icon{margin-top:0}
.al-bar strong{color:#0f172a;font-weight:650}

/* 3. Solid */
.alert.al-solid{--bs-alert-border-color:transparent;--bs-alert-color:#fff;--bs-alert-link-color:#fff;border:0;box-shadow:0 10px 24px -14px var(--bs-alert-bg)}
.al-solid.alert-info{--bs-alert-bg:#1d4ed8}
.al-solid.alert-success{--bs-alert-bg:#15803d}
.al-solid.alert-danger{--bs-alert-bg:#b91c1c}
.al-solid.alert-warning{--bs-alert-bg:#fbbf24;--bs-alert-color:#422006;--bs-alert-link-color:#422006}
.al-solid .al-icon{color:inherit}
.al-solid .btn-close-white:hover{background-color:rgba(255,255,255,.18)}
.al-solid.alert-warning .btn-close:hover{background-color:rgba(66,32,6,.1)}
.al-solid .btn-close{--bs-btn-close-opacity:.85}

@media (prefers-reduced-motion:reduce){.alert.fade{transition:none}}
JavaScript
// Bootstrap removes the alert but leaves focus nowhere. Remember the next alert's control on close
// and focus it once the alert is gone.
document.querySelectorAll('.alert').forEach(function (alert) {
  var next = null;
  alert.addEventListener('close.bs.alert', function () {
    var sibling = alert.nextElementSibling || alert.previousElementSibling;
    next = sibling && sibling.querySelector('.btn-close, a[href], button');
  });
  alert.addEventListener('closed.bs.alert', function () { if (next) next.focus(); });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-info" 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="M12 11v5.5M12 7.6h.01"/></symbol>
  <symbol id="i-ok" 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>
  <symbol id="i-warn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 4.2 2.9 17.3a2 2 0 0 0 1.7 3h14.8a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4.2M12 16.9h.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-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>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Soft with icon, title and dismiss</span>
<div class="flex flex-col gap-3">
  <div class="flex items-start gap-3 rounded-xl border border-[#bfdbfe] bg-[#eff6ff] py-3.5 pr-3 pl-4 text-[.9375rem] text-[#1e40af]" data-alert>
    <svg class="mt-0.5 size-5 flex-none text-[#2563eb]" aria-hidden="true"><use href="#i-info"/></svg>
    <div class="min-w-0 flex-1"><p class="mb-0.5 font-[650] text-[#1e3a8a]">Passkeys are here</p><p>Sign in to Harbor with Face ID or a security key instead of a password. <a href="#" class="rounded-[3px] font-semibold text-[#1e3a8a] underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1e3a8a]">Set up a passkey</a></p></div>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg text-[#1e40af] transition-colors duration-150 hover:bg-[rgba(15,23,42,.07)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1e3a8a] motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Passkeys are here"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl border border-[#bbf7d0] bg-[#f0fdf4] py-3.5 pr-3 pl-4 text-[.9375rem] text-[#166534]" data-alert>
    <svg class="mt-0.5 size-5 flex-none text-[#16a34a]" aria-hidden="true"><use href="#i-ok"/></svg>
    <div class="min-w-0 flex-1"><p class="mb-0.5 font-[650] text-[#14532d]">Payment received</p><p>€1,240.00 from Kestrel Studio has been applied to invoice INV-2093.</p></div>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg text-[#166534] transition-colors duration-150 hover:bg-[rgba(15,23,42,.07)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#14532d] motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Payment received"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl border border-[#fde68a] bg-[#fffbeb] py-3.5 pr-3 pl-4 text-[.9375rem] text-[#854d0e]" data-alert>
    <svg class="mt-0.5 size-5 flex-none text-[#d97706]" aria-hidden="true"><use href="#i-warn"/></svg>
    <div class="min-w-0 flex-1"><p class="mb-0.5 font-[650] text-[#713f12]">Storage almost full</p><p>You have used 9.2 GB of 10 GB. New uploads pause at the limit. <a href="#" class="rounded-[3px] font-semibold text-[#713f12] underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#713f12]">Manage storage</a></p></div>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg text-[#854d0e] transition-colors duration-150 hover:bg-[rgba(15,23,42,.07)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#713f12] motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Storage almost full"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl border border-[#fecaca] bg-[#fef2f2] py-3.5 pr-3 pl-4 text-[.9375rem] text-[#991b1b]" role="alert" data-alert>
    <svg class="mt-0.5 size-5 flex-none text-[#dc2626]" aria-hidden="true"><use href="#i-error"/></svg>
    <div class="min-w-0 flex-1"><p class="mb-0.5 font-[650] text-[#7f1d1d]">Card declined</p><p>Your Visa ending 4417 was declined on 24 September. <a href="#" class="rounded-[3px] font-semibold text-[#7f1d1d] underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7f1d1d]">Update payment details</a> to keep your workspace active.</p></div>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg text-[#991b1b] transition-colors duration-150 hover:bg-[rgba(15,23,42,.07)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#7f1d1d] motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Card declined"><svg class="size-4" 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">Accent bar, one line</span>
<div class="flex flex-col gap-3 text-[.9375rem] text-[#334155]">
  <div class="flex items-center gap-3 rounded-[10px] border border-l-4 border-[#e2e8f0] border-l-[#2563eb] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(15,23,42,.05)]"><svg class="size-5 flex-none text-[#2563eb]" aria-hidden="true"><use href="#i-info"/></svg><p class="min-w-0 flex-1"><span class="sr-only">Information: </span>Harbor is read-only from <strong class="font-[650] text-[#0f172a]">02:00 to 02:10 UTC</strong> on Sunday.</p></div>
  <div class="flex items-center gap-3 rounded-[10px] border border-l-4 border-[#e2e8f0] border-l-[#16a34a] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(15,23,42,.05)]"><svg class="size-5 flex-none text-[#16a34a]" aria-hidden="true"><use href="#i-ok"/></svg><p class="min-w-0 flex-1"><span class="sr-only">Success: </span><strong class="font-[650] text-[#0f172a]">48 contacts</strong> imported from customers-sept.csv.</p></div>
  <div class="flex items-center gap-3 rounded-[10px] border border-l-4 border-[#e2e8f0] border-l-[#d97706] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(15,23,42,.05)]"><svg class="size-5 flex-none text-[#d97706]" aria-hidden="true"><use href="#i-warn"/></svg><p class="min-w-0 flex-1"><span class="sr-only">Warning: </span>Your API key expires in <strong class="font-[650] text-[#0f172a]">5 days</strong>. <a href="#" class="rounded-[3px] font-semibold text-[#1d4ed8] underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1d4ed8]">Rotate key</a></p></div>
  <div class="flex items-center gap-3 rounded-[10px] border border-l-4 border-[#e2e8f0] border-l-[#dc2626] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(15,23,42,.05)]"><svg class="size-5 flex-none text-[#dc2626]" aria-hidden="true"><use href="#i-error"/></svg><p class="min-w-0 flex-1"><span class="sr-only">Error: </span>The Stripe webhook did not respond. Last attempt at <strong class="font-[650] text-[#0f172a]">09:41</strong>.</p></div>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Solid, high emphasis</span>
<div class="flex flex-col gap-3 text-[.9375rem]">
  <div class="flex items-start gap-3 rounded-xl bg-[#1d4ed8] py-3.5 pr-3 pl-4 text-white shadow-[0_10px_24px_-14px_#1d4ed8]" data-alert>
    <svg class="mt-0.5 size-5 flex-none" aria-hidden="true"><use href="#i-info"/></svg>
    <p class="min-w-0 flex-1"><span class="sr-only">Information: </span>Version 4.2 is rolling out to your workspace this week. <a href="#" class="rounded-[3px] font-semibold underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current">What's new</a></p>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg transition-colors duration-150 hover:bg-white/18 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Version 4.2"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl bg-[#15803d] py-3.5 pr-3 pl-4 text-white shadow-[0_10px_24px_-14px_#15803d]" data-alert>
    <svg class="mt-0.5 size-5 flex-none" aria-hidden="true"><use href="#i-ok"/></svg>
    <p class="min-w-0 flex-1"><span class="sr-only">Success: </span>harbor-studio.com is verified and now sends from your domain.</p>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg transition-colors duration-150 hover:bg-white/18 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Domain verified"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl bg-[#fbbf24] py-3.5 pr-3 pl-4 text-[#422006] shadow-[0_10px_24px_-14px_#fbbf24]" data-alert>
    <svg class="mt-0.5 size-5 flex-none" aria-hidden="true"><use href="#i-warn"/></svg>
    <p class="min-w-0 flex-1"><span class="sr-only">Warning: </span>Two-factor authentication is off for 3 admins. <a href="#" class="rounded-[3px] font-semibold underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current">Review admins</a></p>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg transition-colors duration-150 hover:bg-[rgba(66,32,6,.1)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Two-factor warning"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
  <div class="flex items-start gap-3 rounded-xl bg-[#b91c1c] py-3.5 pr-3 pl-4 text-white shadow-[0_10px_24px_-14px_#b91c1c]" data-alert>
    <svg class="mt-0.5 size-5 flex-none" aria-hidden="true"><use href="#i-error"/></svg>
    <p class="min-w-0 flex-1"><span class="sr-only">Error: </span>Sync stopped. 12 files were not uploaded. <a href="#" class="rounded-[3px] font-semibold underline underline-offset-2 hover:decoration-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current">Retry sync</a></p>
    <button class="-my-1 ml-1 grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg transition-colors duration-150 hover:bg-white/18 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current motion-reduce:transition-none" type="button" data-dismiss aria-label="Dismiss: Sync stopped"><svg class="size-4" aria-hidden="true"><use href="#i-x"/></svg></button>
  </div>
</div></div>
JavaScript
// Dismiss: a button with data-dismiss removes its [data-alert]. Focus moves to the next alert's
// button so keyboard users are not sent back to the top of the page.
document.addEventListener('click', function (e) {
  var btn = e.target.closest('[data-dismiss]');
  if (!btn) return;
  var alert = btn.closest('[data-alert]');
  if (!alert) return;
  var sibling = alert.nextElementSibling || alert.previousElementSibling;
  var next = sibling && sibling.querySelector('[data-dismiss], a[href], button');
  var done = function () { alert.remove(); if (next) next.focus(); };
  if (!alert.animate || matchMedia('(prefers-reduced-motion: reduce)').matches) return done();
  alert.animate([{ opacity: 1, transform: 'none' }, { opacity: 0, transform: 'translateY(-4px)' }],
    { duration: 180, easing: 'ease-in' }).onfinish = done;
});

About this set

Twelve alerts in three weights, each in the four statuses every interface needs: info, success, warning and danger. Soft alerts are tinted cards with an icon, a bold title, a sentence with an optional link and a dismiss button, for messages that explain something, such as a declined card or a storage limit. Accent bar alerts are white one-line cards with a 4px status stripe, for compact notices that should not pull focus, such as a read-only window or an expiring API key. Solid alerts are filled with the status colour for the one message on a page that must not be missed. Every alert reads its colours from a small set of custom properties, so a new status is one line of CSS. The status is never carried by colour alone: soft alerts have a title that names it, and the one-line and solid alerts start with visually hidden text such as Warning or Error. Only the declined card uses role=”alert”, because only that one is urgent. Dismissing is a short delegated script that removes the alert and moves focus to the next alert’s button so keyboard users keep their place. The Bootstrap version uses .alert, .alert-link and the Alert plugin.

What’s included

  • Four statuses in three weights: soft, accent bar and solid
  • Colours set by five custom properties per status
  • Status named in text, not only by colour or icon
  • Dismiss moves focus to the next alert instead of the top of the page
  • Bootstrap .alert restyled through its --bs-alert-* variables

Accessibility

Each dismiss button is named after its alert, for example Dismiss: Card declined, and shows a 2px outline on keyboard focus. Icons are hidden from assistive technology and the status is given in text, with visually hidden prefixes on the one-line and solid alerts. Only the urgent declined-card alert has role="alert". The dismiss fade and hover transitions switch off under prefers-reduced-motion.

Customise it

In plain CSS change --al-bg, --al-line, --al-icon, --al-title and --al-ink on each .al-* status class, and --al-solid for the filled style. In Bootstrap set the --bs-alert-* variables on .alert-info, .alert-success, .alert-warning and .alert-danger; in Tailwind edit the hex values in the bg-, border- and text- utilities.