Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-checks" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 12.5 4 4 8-9M12 16l1 1 8-9"/></symbol>
  <symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 15c-1.5 1.3-2 5-2 5s3.7-.5 5-2c.7-.8.7-2.1-.1-2.9a2.2 2.2 0 0 0-2.9-.1z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.9A12.9 12.9 0 0 1 22 2c0 2.7-.8 7.5-6 11a22.4 22.4 0 0 1-4 2z"/></symbol>
  <symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></symbol>
  <symbol id="i-pr" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M6 8.5v7M18 15.5V9a3 3 0 0 0-3-3h-4M13 3.5 10.5 6 13 8.5"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.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.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4M12 17h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M7 7l10 10M17 7 7 17"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
</svg>

<div><span class="lbl">Bell and panel on a dark app bar</span>
<div class="dk-frame">
  <header class="dk-bar">
    <span class="dk-logo"><i aria-hidden="true"></i>Orbit</span>
    <div class="nf" data-nf>
      <button class="dk-bell" type="button" aria-expanded="true" aria-controls="dk-panel" data-nf-bell>
        <svg aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="dk-count" data-nf-count>3<span class="sr-only"> unread</span></span>
      </button>
      <section class="dk-panel" id="dk-panel" aria-labelledby="dk-title" data-nf-panel>
        <div class="dk-head"><h2 id="dk-title">Notifications</h2><span class="dk-new" data-nf-new>3 new</span>
          <button class="dk-markall" type="button" data-nf-markall><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <ul class="dk-list">
          <li><a class="nf-item is-unread" href="#"><span class="dk-av ok" aria-hidden="true"><svg><use href="#i-rocket"/></svg></span><span><b>api-gateway v3.8.1</b> deployed to production in 2m 14s<span class="dk-time">4 min ago</span></span><span class="dk-unread"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item is-unread" href="#"><span class="dk-av warn" aria-hidden="true"><svg><use href="#i-cpu"/></svg></span><span>CPU on <b>worker-eu-3</b> above 90% for 10 minutes<span class="dk-time">18 min ago</span></span><span class="dk-unread"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item is-unread" href="#"><span class="dk-av c1" aria-hidden="true">YN</span><span><b>Yuki Nakamura</b> requested your review on <b>#482 Rate limit per token</b><span class="dk-time">42 min ago</span></span><span class="dk-unread"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item" href="#"><span class="dk-av c2" aria-hidden="true">OB</span><span><b>Omar Bakr</b> merged <b>#479 Retry webhooks with backoff</b><span class="dk-time">2 h ago</span></span><span class="dk-unread"></span></a></li>
          <li><a class="nf-item" href="#"><span class="dk-av ok" aria-hidden="true"><svg><use href="#i-ok"/></svg></span><span>Nightly backup of <b>orbit-prod</b> finished: 14.2 GB<span class="dk-time">6 h ago</span></span><span class="dk-unread"></span></a></li>
        </ul>
        <div class="dk-foot"><a class="dk-link" href="#">Open inbox</a><span>Synced just now</span></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
  </header>
</div></div>

<div><span class="lbl">Toasts with status glow and a log line</span>
<div class="dk-stack" role="status" aria-live="polite" aria-atomic="false" aria-label="Notifications" data-dt-stack>
    <div class="dk-toast ok" data-dt>
      <span class="dk-ticon" aria-hidden="true"><svg><use href="#i-ok"/></svg></span>
      <div class="dk-tbody"><p class="dk-ttitle">Deploy finished</p><p class="dk-ttext">web-app v5.2.0 is live on orbit.dev.</p><a class="dk-act" href="#">View deploy</a></div>
      <button class="dk-x" type="button" aria-label="Dismiss: Deploy finished" data-dt-close><svg aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="dk-toast warn" data-dt>
      <span class="dk-ticon" aria-hidden="true"><svg><use href="#i-warn"/></svg></span>
      <div class="dk-tbody"><p class="dk-ttitle">High memory on cache-2</p><p class="dk-ttext">Using 7.4 of 8 GB. Eviction starts at 95%.</p></div>
      <button class="dk-x" type="button" aria-label="Dismiss: High memory on cache-2" data-dt-close><svg aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="dk-toast err" role="alert" data-dt>
      <span class="dk-ticon" aria-hidden="true"><svg><use href="#i-error"/></svg></span>
      <div class="dk-tbody"><p class="dk-ttitle">Build #1294 failed</p><p class="dk-ttext">Step “test” exited with code 1.</p><code class="dk-code">FAIL src/billing/invoice.test.ts › rounds tax to 2 places</code><a class="dk-act" href="#">Open logs</a></div>
      <button class="dk-x" type="button" aria-label="Dismiss: Build #1294 failed" data-dt-close><svg aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
</div></div>
CSS
/* Dark notifications: the bell panel and toasts from the light sets, retuned for a near-black UI. Every text colour
   was checked for 4.5:1 on its surface; glows are box-shadows, so they add no contrast the text depends on. */
.dk-frame,.dk-stack{--dk-bg:#0b0d14;--dk-surface:#131722;--dk-raised:#1a1f2e;--dk-line:#262c3d;--dk-ink:#f1f5f9;--dk-text:#c3c9d9;--dk-muted:#8b93a7;--dk-brand:#818cf8;--dk-focus:#a5b4fc}
.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}
.dk-frame [hidden],.dk-stack [hidden]{display:none!important}
.dk-frame{position:relative;height:600px;border:1px solid var(--dk-line);border-radius:18px;background:radial-gradient(80% 60% at 85% 0%,rgba(129,140,248,.14),transparent 60%),var(--dk-bg)}
.dk-bar{position:relative;display:flex;align-items:center;justify-content:space-between;height:62px;padding:0 18px;border-bottom:1px solid var(--dk-line)}
.dk-logo{display:flex;align-items:center;gap:9px;color:var(--dk-ink);font-weight:750}
.dk-logo i{width:24px;height:24px;border-radius:50%;background:conic-gradient(from 200deg,#818cf8,#22d3ee,#818cf8);box-shadow:0 0 16px rgba(129,140,248,.6)}
.nf{position:relative}
.dk-bell{position:relative;display:grid;place-items:center;width:40px;height:40px;padding:0;border:1px solid var(--dk-line);border-radius:12px;background:var(--dk-surface);color:var(--dk-text);cursor:pointer}
.dk-bell:hover,.dk-bell[aria-expanded="true"]{color:var(--dk-ink);border-color:#3b4258;background:var(--dk-raised)}
.dk-bell svg{width:20px;height:20px}
.dk-count{position:absolute;top:-6px;right:-6px;min-width:20px;height:20px;padding:0 5px;border:2px solid var(--dk-bg);border-radius:999px;background:#e11d48;color:#fff;
  font-size:.6875rem;font-weight:700;line-height:16px;text-align:center;box-shadow:0 0 12px rgba(225,29,72,.7)}
.dk-panel{position:absolute;top:calc(100% + 12px);right:-6px;z-index:20;width:400px;max-width:calc(100vw - 32px);border:1px solid var(--dk-line);border-radius:16px;background:var(--dk-surface);color:var(--dk-text);
  font-size:.875rem;line-height:1.45;box-shadow:0 30px 60px -20px rgba(0,0,0,.7),0 0 0 1px rgba(129,140,248,.06);animation:dk-in .18s ease-out}
.dk-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:16px 16px 12px 18px;border-bottom:1px solid var(--dk-line)}
.dk-head h2{margin:0;font-size:1rem;color:var(--dk-ink)}
.dk-new{padding:2px 8px;border-radius:999px;background:rgba(129,140,248,.16);color:#c7d2fe;font-size:.75rem;font-weight:650;white-space:nowrap}
.dk-markall{display:inline-flex;align-items:center;gap:6px;margin-left:auto;padding:5px 8px;border:0;border-radius:8px;background:transparent;color:#c7d2fe;font:inherit;font-size:.8125rem;font-weight:600;white-space:nowrap;cursor:pointer}
.dk-markall:hover{background:rgba(129,140,248,.12)}
.dk-markall:disabled{color:var(--dk-muted);background:transparent;cursor:default}
.dk-markall svg{width:16px;height:16px}
.dk-list{margin:0;padding:8px;list-style:none;max-height:390px;overflow-y:auto}
.dk-list li+li{margin-top:2px}
.nf-item{position:relative;display:flex;gap:12px;padding:11px 30px 11px 10px;border-radius:12px;color:var(--dk-text);text-decoration:none}
.nf-item:hover{background:var(--dk-raised)}
.nf-item.is-unread{background:rgba(129,140,248,.08)}
.nf-item b{color:var(--dk-ink);font-weight:650}
.dk-time{display:block;margin-top:4px;font-size:.75rem;color:var(--dk-muted)}
.dk-unread{position:absolute;top:50%;right:12px;width:8px;height:8px;margin-top:-4px;border-radius:50%;background:#a5b4fc;box-shadow:0 0 0 3px rgba(129,140,248,.2),0 0 12px rgba(165,180,252,.9)}
.nf-item:not(.is-unread) .dk-unread{display:none}
.dk-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;font-size:.75rem;font-weight:700}
.dk-av svg{width:18px;height:18px}
.dk-av.ok{background:rgba(74,222,128,.12);color:#4ade80}.dk-av.warn{background:rgba(251,191,36,.12);color:#fbbf24}
.dk-av.c1{background:#312e81;color:#c7d2fe}.dk-av.c2{background:#164e63;color:#a5f3fc}
.dk-foot{display:flex;justify-content:space-between;align-items:center;padding:10px 12px 12px 18px;border-top:1px solid var(--dk-line);font-size:.75rem;color:var(--dk-muted)}
.dk-link{padding:4px 0;color:#c7d2fe;font-size:.875rem;font-weight:600;text-decoration:none}
.dk-link:hover{text-decoration:underline}
/* Toasts */
.dk-stack{display:flex;flex-direction:column;gap:12px;max-width:420px;padding:22px;border:1px solid var(--dk-line);border-radius:18px;background:var(--dk-bg)}
.dk-toast{--dk-tone:#4ade80;position:relative;display:flex;align-items:flex-start;gap:12px;padding:14px 10px 14px 14px;overflow:hidden;border:1px solid var(--dk-line);border-radius:14px;
  background:linear-gradient(90deg,color-mix(in srgb,var(--dk-tone) 10%,transparent),transparent 45%),var(--dk-surface);color:var(--dk-text);font-size:.875rem;line-height:1.45;
  box-shadow:0 16px 36px -16px rgba(0,0,0,.8);animation:dk-in .3s cubic-bezier(.2,.8,.2,1)}
.dk-toast::before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:0 3px 3px 0;background:var(--dk-tone);box-shadow:0 0 12px var(--dk-tone)}
.dk-toast.warn{--dk-tone:#fbbf24}
.dk-toast.err{--dk-tone:#fb7185}
.dk-ticon{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:color-mix(in srgb,var(--dk-tone) 14%,transparent);color:var(--dk-tone)}
.dk-ticon svg{width:16px;height:16px}
.dk-tbody{flex:1;min-width:0}
.dk-ttitle{margin:0;font-size:.9375rem;font-weight:650;color:var(--dk-ink)}
.dk-ttext{margin:2px 0 0}
.dk-code{display:block;margin-top:8px;padding:8px 10px;border-radius:8px;background:#05070c;border:1px solid var(--dk-line);color:#fda4af;font:500 .75rem/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
.dk-act{display:inline-block;margin-top:8px;color:#c7d2fe;font-weight:650;text-decoration:none}
.dk-act:hover{text-decoration:underline}
.dk-x{flex:none;display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;border-radius:8px;background:transparent;color:var(--dk-muted);cursor:pointer}
.dk-x:hover{background:var(--dk-raised);color:var(--dk-ink)}
.dk-x svg{width:14px;height:14px}
.dk-bell:focus-visible,.dk-markall:focus-visible,.nf-item:focus-visible,.dk-link:focus-visible,.dk-act:focus-visible,.dk-x:focus-visible{outline:2px solid var(--dk-focus);outline-offset:2px}
.nf-item:focus-visible{outline-offset:-2px}
@keyframes dk-in{from{opacity:0;transform:translateY(-6px)}}
@media (max-width:520px){.dk-frame{height:680px}.nf{position:static}.dk-panel{left:8px;right:8px;width:auto;max-width:none}.dk-stack{padding:14px}}
@media (prefers-reduced-motion:reduce){.dk-panel,.dk-toast{animation:none}}
JavaScript
(function () {
  // Bell panel: same pattern as a light dropdown. Open/close, Esc, outside click, mark read.
    document.querySelectorAll('[data-nf]').forEach(function (nf) {
      var bell = nf.querySelector('[data-nf-bell]');
      var panel = nf.querySelector('[data-nf-panel]');
      if (!bell || !panel) return;
      var count = nf.querySelector('[data-nf-count]');
      var badge = nf.querySelector('[data-nf-new]');
      var markAll = nf.querySelector('[data-nf-markall]');
      var status = nf.querySelector('[data-nf-status]');

      function setOpen(open) {
        bell.setAttribute('aria-expanded', open);
        panel.hidden = !open;
      }
      function refresh() {
        var n = panel.querySelectorAll('.is-unread').length;
        if (count) {
          count.hidden = n === 0;
          if (count.firstChild && count.firstChild.nodeType === 3) count.firstChild.nodeValue = n;
        }
        if (badge) { badge.hidden = n === 0; badge.textContent = n + ' new'; }
        if (markAll) markAll.disabled = n === 0;
      }
      function read(item) {
        item.classList.remove('is-unread');
        var sr = item.querySelector('.sr-only');
        if (sr) sr.remove();
      }

      bell.addEventListener('click', function () { setOpen(panel.hidden); });
      markAll && markAll.addEventListener('click', function () {
        panel.querySelectorAll('.is-unread').forEach(read);
        refresh();
        if (status) status.textContent = 'All notifications marked as read';
      });
      panel.addEventListener('click', function (e) {
        var item = e.target.closest('.nf-item');
        if (!item) return;
        e.preventDefault(); // demo links; remove when they point somewhere
        read(item); refresh();
      });
      // Esc closes and returns focus to the bell; a click outside closes.
      nf.addEventListener('keydown', function (e) {
        if (e.key === 'Escape' && !panel.hidden) { setOpen(false); bell.focus(); }
      });
      document.addEventListener('click', function (e) {
        if (!panel.hidden && !nf.contains(e.target)) setOpen(false);
      });
      refresh();
    });

  // Toasts: the close button removes the toast and moves focus to the next one.
  document.querySelectorAll('[data-dt-stack]').forEach(function (stack) {
    stack.addEventListener('click', function (e) {
      var btn = e.target.closest('[data-dt-close]');
      if (!btn) return;
      var toast = btn.closest('[data-dt]');
      var next = toast.nextElementSibling || toast.previousElementSibling;
      toast.remove();
      var target = next && next.querySelector('[data-dt-close]');
      if (target) target.focus();
    });
  });
})();

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-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-checks" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 12.5 4 4 8-9M12 16l1 1 8-9"/></symbol>
  <symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 15c-1.5 1.3-2 5-2 5s3.7-.5 5-2c.7-.8.7-2.1-.1-2.9a2.2 2.2 0 0 0-2.9-.1z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.9A12.9 12.9 0 0 1 22 2c0 2.7-.8 7.5-6 11a22.4 22.4 0 0 1-4 2z"/></symbol>
  <symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></symbol>
  <symbol id="i-pr" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M6 8.5v7M18 15.5V9a3 3 0 0 0-3-3h-4M13 3.5 10.5 6 13 8.5"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.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.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4M12 17h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M7 7l10 10M17 7 7 17"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
</svg>

<div><span class="lbl">Bell and panel on a dark app bar</span>
<div class="dk-frame">
  <header class="dk-bar d-flex align-items-center justify-content-between">
    <span class="dk-logo d-flex align-items-center gap-2"><i aria-hidden="true"></i>Orbit</span>
    <div class="dropdown nf" data-nf>
      <button class="btn dk-bell show" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" data-bs-display="static" aria-expanded="true">
        <svg aria-hidden="true"><use href="#i-bell"/></svg><span class="visually-hidden">Notifications</span>
        <span class="badge rounded-pill dk-count" data-nf-count>3<span class="visually-hidden"> unread</span></span>
      </button>
      <section class="dropdown-menu dropdown-menu-end dk-panel show" data-bs-popper="static" aria-labelledby="dk-title">
        <div class="dk-head d-flex flex-wrap align-items-center gap-2"><h2 id="dk-title">Notifications</h2><span class="badge rounded-pill dk-new" data-nf-new>3 new</span>
          <button class="btn dk-markall d-inline-flex align-items-center gap-1 ms-auto" type="button" data-nf-markall><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <ul class="list-unstyled m-0 dk-list">
          <li><a class="dropdown-item nf-item is-unread" href="#"><span class="dk-av ok" aria-hidden="true"><svg><use href="#i-rocket"/></svg></span><span><b>api-gateway v3.8.1</b> deployed to production in 2m 14s<span class="dk-time">4 min ago</span></span><span class="dk-unread"></span><span class="visually-hidden">(unread)</span></a></li>
          <li><a class="dropdown-item nf-item is-unread" href="#"><span class="dk-av warn" aria-hidden="true"><svg><use href="#i-cpu"/></svg></span><span>CPU on <b>worker-eu-3</b> above 90% for 10 minutes<span class="dk-time">18 min ago</span></span><span class="dk-unread"></span><span class="visually-hidden">(unread)</span></a></li>
          <li><a class="dropdown-item nf-item is-unread" href="#"><span class="dk-av c1" aria-hidden="true">YN</span><span><b>Yuki Nakamura</b> requested your review on <b>#482 Rate limit per token</b><span class="dk-time">42 min ago</span></span><span class="dk-unread"></span><span class="visually-hidden">(unread)</span></a></li>
          <li><a class="dropdown-item nf-item" href="#"><span class="dk-av c2" aria-hidden="true">OB</span><span><b>Omar Bakr</b> merged <b>#479 Retry webhooks with backoff</b><span class="dk-time">2 h ago</span></span><span class="dk-unread"></span></a></li>
          <li><a class="dropdown-item nf-item" href="#"><span class="dk-av ok" aria-hidden="true"><svg><use href="#i-ok"/></svg></span><span>Nightly backup of <b>orbit-prod</b> finished: 14.2 GB<span class="dk-time">6 h ago</span></span><span class="dk-unread"></span></a></li>
        </ul>
        <div class="dk-foot d-flex justify-content-between align-items-center"><a class="dk-link" href="#">Open inbox</a><span>Synced just now</span></div>
        <p class="visually-hidden" role="status" data-nf-status></p>
      </section>
    </div>
  </header>
</div></div>

<div><span class="lbl">Toasts with status glow and a log line</span>
<div class="toast-container position-static dk-stack" role="status" aria-live="polite" aria-atomic="false" aria-label="Notifications" data-dt-stack>
    <div class="toast dk-toast ok fade show" data-bs-autohide="false">
      <div class="toast-body d-flex align-items-start gap-3"><span class="dk-ticon" aria-hidden="true"><svg><use href="#i-ok"/></svg></span>
      <div class="flex-grow-1 dk-tbody"><p class="dk-ttitle">Deploy finished</p><p class="dk-ttext">web-app v5.2.0 is live on orbit.dev.</p><a class="dk-act" href="#">View deploy</a></div>
      <button class="btn-close" type="button" data-bs-dismiss="toast" aria-label="Dismiss: Deploy finished"></button></div>
    </div>
    <div class="toast dk-toast warn fade show" data-bs-autohide="false">
      <div class="toast-body d-flex align-items-start gap-3"><span class="dk-ticon" aria-hidden="true"><svg><use href="#i-warn"/></svg></span>
      <div class="flex-grow-1 dk-tbody"><p class="dk-ttitle">High memory on cache-2</p><p class="dk-ttext">Using 7.4 of 8 GB. Eviction starts at 95%.</p></div>
      <button class="btn-close" type="button" data-bs-dismiss="toast" aria-label="Dismiss: High memory on cache-2"></button></div>
    </div>
    <div class="toast dk-toast err fade show" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false">
      <div class="toast-body d-flex align-items-start gap-3"><span class="dk-ticon" aria-hidden="true"><svg><use href="#i-error"/></svg></span>
      <div class="flex-grow-1 dk-tbody"><p class="dk-ttitle">Build #1294 failed</p><p class="dk-ttext">Step “test” exited with code 1.</p><code class="dk-code">FAIL src/billing/invoice.test.ts › rounds tax to 2 places</code><a class="dk-act" href="#">Open logs</a></div>
      <button class="btn-close" type="button" data-bs-dismiss="toast" aria-label="Dismiss: Build #1294 failed"></button></div>
    </div>
</div></div>
CSS
/* Dark notifications on Bootstrap in data-bs-theme="dark": a .dropdown bell panel and .toast components, retuned
   through Bootstrap variables for a near-black UI. Text colours were checked for 4.5:1 on their surfaces. */
.dk-frame,.dk-stack{--dk-bg:#0b0d14;--dk-surface:#131722;--dk-raised:#1a1f2e;--dk-line:#262c3d;--dk-ink:#f1f5f9;--dk-text:#c3c9d9;--dk-muted:#8b93a7;--dk-brand:#818cf8;--dk-focus:#a5b4fc}
.dk-frame{position:relative;height:600px;border:1px solid var(--dk-line);border-radius:18px;background:radial-gradient(80% 60% at 85% 0%,rgba(129,140,248,.14),transparent 60%),var(--dk-bg)}
.dk-bar{position:relative;height:62px;padding:0 18px;border-bottom:1px solid var(--dk-line)}
.dk-logo{color:var(--dk-ink);font-weight:750}
.dk-logo i{width:24px;height:24px;border-radius:50%;background:conic-gradient(from 200deg,#818cf8,#22d3ee,#818cf8);box-shadow:0 0 16px rgba(129,140,248,.6)}
.dk-bell{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-color:var(--dk-text);--bs-btn-bg:var(--dk-surface);--bs-btn-border-color:var(--dk-line);--bs-btn-hover-color:var(--dk-ink);--bs-btn-hover-bg:var(--dk-raised);
  --bs-btn-hover-border-color:#3b4258;--bs-btn-active-color:var(--dk-ink);--bs-btn-active-bg:var(--dk-raised);--bs-btn-active-border-color:#3b4258;--bs-btn-border-radius:12px;--bs-btn-focus-box-shadow:0 0 0 2px var(--dk-focus);
  position:relative;display:grid;place-items:center;width:40px;height:40px}
.dk-bell svg{width:20px;height:20px}
.dk-bell .dk-count{--bs-badge-padding-x:5px;--bs-badge-padding-y:0;--bs-badge-font-size:.6875rem;position:absolute;top:-6px;right:-6px;min-width:20px;height:20px;line-height:16px;border:2px solid var(--dk-bg);background:#e11d48;box-shadow:0 0 12px rgba(225,29,72,.7)}
.dk-panel{--bs-dropdown-bg:var(--dk-surface);--bs-dropdown-border-color:var(--dk-line);--bs-dropdown-border-radius:16px;--bs-dropdown-padding-y:0;--bs-dropdown-spacer:12px;--bs-dropdown-zindex:20;--bs-dropdown-color:var(--dk-text);
  --bs-dropdown-link-color:var(--dk-text);--bs-dropdown-link-hover-bg:var(--dk-raised);--bs-dropdown-link-hover-color:var(--dk-text);--bs-dropdown-link-active-bg:var(--dk-raised);--bs-dropdown-link-active-color:var(--dk-text);
  width:400px;max-width:calc(100vw - 32px);font-size:.875rem;line-height:1.45;box-shadow:0 30px 60px -20px rgba(0,0,0,.7),0 0 0 1px rgba(129,140,248,.06)}
.dk-panel.show{animation:dk-in .18s ease-out}
.dk-panel.dropdown-menu-end[data-bs-popper]{right:-6px}
.dk-head{padding:16px 16px 12px 18px;border-bottom:1px solid var(--dk-line)}
.dk-head h2{margin:0;font-size:1rem;font-weight:700;color:var(--dk-ink)}
.dk-new{--bs-badge-font-size:.75rem;--bs-badge-font-weight:650;--bs-badge-color:#c7d2fe;background:rgba(129,140,248,.16)}
.dk-markall{--bs-btn-padding-x:8px;--bs-btn-padding-y:5px;--bs-btn-font-size:.8125rem;--bs-btn-font-weight:600;--bs-btn-color:#c7d2fe;--bs-btn-hover-color:#c7d2fe;--bs-btn-hover-bg:rgba(129,140,248,.12);--bs-btn-active-color:#c7d2fe;
  --bs-btn-border-width:0;--bs-btn-border-radius:8px;--bs-btn-disabled-color:var(--dk-muted);--bs-btn-disabled-opacity:1;--bs-btn-focus-box-shadow:0 0 0 2px var(--dk-focus);white-space:nowrap}
.dk-markall svg{width:16px;height:16px}
.dk-list{padding:8px!important;max-height:390px;overflow-y:auto}
.dk-list li+li{margin-top:2px}
.nf-item{position:relative;display:flex;gap:12px;padding:11px 30px 11px 10px;border-radius:12px;white-space:normal}
.nf-item.is-unread{background:rgba(129,140,248,.08)}
.nf-item:focus-visible{outline:2px solid var(--dk-focus);outline-offset:-2px}
.nf-item b{color:var(--dk-ink);font-weight:650}
.dk-time{display:block;margin-top:4px;font-size:.75rem;color:var(--dk-muted)}
.dk-unread{position:absolute;top:50%;right:12px;width:8px;height:8px;margin-top:-4px;border-radius:50%;background:#a5b4fc;box-shadow:0 0 0 3px rgba(129,140,248,.2),0 0 12px rgba(165,180,252,.9)}
.nf-item:not(.is-unread) .dk-unread{display:none}
.dk-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;font-size:.75rem;font-weight:700}
.dk-av svg{width:18px;height:18px}
.dk-av.ok{background:rgba(74,222,128,.12);color:#4ade80}.dk-av.warn{background:rgba(251,191,36,.12);color:#fbbf24}
.dk-av.c1{background:#312e81;color:#c7d2fe}.dk-av.c2{background:#164e63;color:#a5f3fc}
.dk-foot{padding:10px 12px 12px 18px;border-top:1px solid var(--dk-line);font-size:.75rem;color:var(--dk-muted)}
.dk-link{padding:4px 0;color:#c7d2fe;font-size:.875rem;font-weight:600;text-decoration:none}
.dk-link:hover{color:#c7d2fe;text-decoration:underline}
.dk-stack{--bs-toast-spacing:12px;width:auto;max-width:420px;padding:22px;border:1px solid var(--dk-line);border-radius:18px;background:var(--dk-bg);pointer-events:auto}
.dk-toast{--dk-tone:#4ade80;--bs-toast-spacing:12px;--bs-toast-max-width:100%;--bs-toast-bg:var(--dk-surface);--bs-toast-border-color:var(--dk-line);--bs-toast-border-radius:14px;--bs-toast-color:var(--dk-text);--bs-toast-font-size:.875rem;
  --bs-toast-box-shadow:0 16px 36px -16px rgba(0,0,0,.8);position:relative;overflow:hidden;line-height:1.45;
  background:linear-gradient(90deg,color-mix(in srgb,var(--dk-tone) 10%,transparent),transparent 45%),var(--dk-surface)}
.dk-toast.show{animation:dk-in .3s cubic-bezier(.2,.8,.2,1)}
.dk-toast::before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:3px;border-radius:0 3px 3px 0;background:var(--dk-tone);box-shadow:0 0 12px var(--dk-tone)}
.dk-toast.warn{--dk-tone:#fbbf24}
.dk-toast.err{--dk-tone:#fb7185}
.dk-toast .toast-body{padding:14px 10px 14px 14px}
.dk-ticon{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:color-mix(in srgb,var(--dk-tone) 14%,transparent);color:var(--dk-tone)}
.dk-ticon svg{width:16px;height:16px}
.dk-tbody{min-width:0}
.dk-ttitle{margin:0;font-size:.9375rem;font-weight:650;color:var(--dk-ink)}
.dk-ttext{margin:2px 0 0}
.dk-code{display:block;margin-top:8px;padding:8px 10px;border-radius:8px;background:#05070c;border:1px solid var(--dk-line);color:#fda4af;font:500 .75rem/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;overflow-wrap:anywhere}
.dk-act{display:inline-block;margin-top:8px;color:#c7d2fe;font-weight:650;text-decoration:none}
.dk-act:hover{color:#c7d2fe;text-decoration:underline}
.dk-toast .btn-close{--bs-btn-close-focus-shadow:0 0 0 2px var(--dk-focus);flex:none;width:30px;height:30px;padding:0;border-radius:8px;background-size:10px}
.dk-link:focus-visible,.dk-act:focus-visible{outline:2px solid var(--dk-focus);outline-offset:2px}
@keyframes dk-in{from{opacity:0;transform:translateY(-6px)}}
@media (max-width:520px){.dk-frame{height:680px}.nf{position:static}.dk-bar .dk-panel[data-bs-popper]{left:8px;right:8px;width:auto;max-width:none}.dk-stack{padding:14px}}
@media (prefers-reduced-motion:reduce){.dk-panel.show,.dk-toast.show{animation:none}.dk-toast.fade{transition:none}}
JavaScript
(function () {
  // Bell: Bootstrap's dropdown plugin opens and closes the panel; this marks notifications as read.
    document.querySelectorAll('[data-nf]').forEach(function (nf) {
      var count = nf.querySelector('[data-nf-count]');
      var badge = nf.querySelector('[data-nf-new]');
      var markAll = nf.querySelector('[data-nf-markall]');
      var status = nf.querySelector('[data-nf-status]');
      var toggle = nf.querySelector('[data-bs-toggle="dropdown"]');
      if (toggle) bootstrap.Dropdown.getOrCreateInstance(toggle); // so a dropdown that starts open closes on outside click

      function refresh() {
        var n = nf.querySelectorAll('.nf-item.is-unread').length;
        if (count) {
          count.classList.toggle('d-none', n === 0);
          if (count.firstChild && count.firstChild.nodeType === 3) count.firstChild.nodeValue = n;
        }
        if (badge) { badge.classList.toggle('d-none', n === 0); badge.textContent = n + ' new'; }
        if (markAll) markAll.disabled = n === 0;
      }
      function read(item) {
        item.classList.remove('is-unread');
        var sr = item.querySelector('.visually-hidden');
        if (sr) sr.remove();
      }
      markAll && markAll.addEventListener('click', function () {
        nf.querySelectorAll('.nf-item.is-unread').forEach(read);
        refresh();
        if (status) status.textContent = 'All notifications marked as read';
      });
      nf.addEventListener('click', function (e) {
        var item = e.target.closest('.nf-item');
        if (!item) return;
        e.preventDefault(); // demo links; remove when they point somewhere
        read(item); refresh();
      });
      refresh();
    });

  // Toasts: Bootstrap's Toast plugin hides them (data-bs-dismiss); remove the element and move focus on.
  document.querySelectorAll('[data-dt-stack] .toast').forEach(function (toast) {
    var hadFocus = false, next = null;
    toast.addEventListener('hide.bs.toast', function () {
      hadFocus = toast.contains(document.activeElement);
      next = toast.nextElementSibling || toast.previousElementSibling;
    });
    toast.addEventListener('hidden.bs.toast', function () {
      toast.remove();
      var target = hadFocus && next && next.querySelector('.btn-close');
      if (target) target.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-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.9 1.9 0 0 0 3.4 0"/></symbol>
  <symbol id="i-checks" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 12.5 4 4 8-9M12 16l1 1 8-9"/></symbol>
  <symbol id="i-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 15c-1.5 1.3-2 5-2 5s3.7-.5 5-2c.7-.8.7-2.1-.1-2.9a2.2 2.2 0 0 0-2.9-.1z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.9A12.9 12.9 0 0 1 22 2c0 2.7-.8 7.5-6 11a22.4 22.4 0 0 1-4 2z"/></symbol>
  <symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/></symbol>
  <symbol id="i-pr" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path d="M6 8.5v7M18 15.5V9a3 3 0 0 0-3-3h-4M13 3.5 10.5 6 13 8.5"/></symbol>
  <symbol id="i-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12.5 4.5 4.5L19 7.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.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0z"/><path d="M12 9.5v4M12 17h.01"/></symbol>
  <symbol id="i-error" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M7 7l10 10M17 7 7 17"/></symbol>
  <symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="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-[#8b93a7] uppercase">Bell and panel on a dark app bar</span>
<div class="relative h-[600px] rounded-[18px] border border-[#262c3d] bg-[#0b0d14] bg-[image:radial-gradient(80%_60%_at_85%_0%,rgba(129,140,248,.14),transparent_60%)] max-[520px]:h-[680px]">
  <header class="relative flex h-[62px] items-center justify-between border-b border-[#262c3d] px-[18px]">
    <span class="flex items-center gap-[9px] font-[750] text-[#f1f5f9]"><i class="size-6 rounded-full bg-[conic-gradient(from_200deg,#818cf8,#22d3ee,#818cf8)] shadow-[0_0_16px_rgba(129,140,248,.6)]" aria-hidden="true"></i>Orbit</span>
    <div class="relative max-[520px]:static" data-nf>
      <button class="relative grid size-10 cursor-pointer place-items-center rounded-xl border border-[#262c3d] bg-[#131722] p-0 text-[#c3c9d9] hover:border-[#3b4258] hover:bg-[#1a1f2e] hover:text-[#f1f5f9] aria-expanded:border-[#3b4258] aria-expanded:bg-[#1a1f2e] aria-expanded:text-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" aria-expanded="true" aria-controls="dk-panel" data-nf-bell>
        <svg class="size-5" aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="absolute -top-1.5 -right-1.5 h-5 min-w-5 rounded-full border-2 border-[#0b0d14] bg-[#e11d48] px-[5px] text-center text-[.6875rem] leading-4 font-bold text-white shadow-[0_0_12px_rgba(225,29,72,.7)]" data-nf-count>3<span class="sr-only"> unread</span></span>
      </button>
      <section class="absolute top-[calc(100%+12px)] -right-1.5 z-20 w-[400px] max-w-[calc(100vw-32px)] animate-dk-in rounded-2xl border border-[#262c3d] bg-[#131722] text-sm leading-[1.45] text-[#c3c9d9] shadow-[0_30px_60px_-20px_rgba(0,0,0,.7),0_0_0_1px_rgba(129,140,248,.06)] motion-reduce:animate-none max-[520px]:right-2 max-[520px]:left-2 max-[520px]:w-auto max-[520px]:max-w-none" id="dk-panel" aria-labelledby="dk-title" data-nf-panel>
        <div class="flex flex-wrap items-center gap-2.5 border-b border-[#262c3d] pt-4 pr-4 pb-3 pl-[18px]"><h2 class="m-0 text-base font-bold text-[#f1f5f9]" id="dk-title">Notifications</h2><span class="rounded-full bg-[#818cf8]/16 px-2 py-0.5 text-xs font-[650] whitespace-nowrap text-[#c7d2fe]" data-nf-new>3 new</span>
          <button class="ml-auto inline-flex cursor-pointer items-center gap-1.5 rounded-lg border-0 bg-transparent px-2 py-[5px] text-[.8125rem] font-semibold whitespace-nowrap text-[#c7d2fe] hover:bg-[#818cf8]/12 disabled:cursor-default disabled:bg-transparent disabled:text-[#8b93a7] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" data-nf-markall><svg class="size-4" aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <ul class="m-0 max-h-[390px] list-none space-y-0.5 overflow-y-auto p-2">
          <li><a class="nf-item group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-[#c3c9d9] no-underline hover:bg-[#1a1f2e] [&.is-unread]:bg-[#818cf8]/8 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] [&_b]:font-[650] [&_b]:text-[#f1f5f9] is-unread" href="#"><span class="grid size-9 flex-none place-items-center rounded-full text-xs font-bold bg-[#4ade80]/12 text-[#4ade80]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-rocket"/></svg></span><span><b>api-gateway v3.8.1</b> deployed to production in 2m 14s<span class="mt-1 block text-xs text-[#8b93a7]">4 min ago</span></span><span class="absolute top-1/2 right-3 -mt-1 hidden size-2 rounded-full bg-[#a5b4fc] shadow-[0_0_0_3px_rgba(129,140,248,.2),0_0_12px_rgba(165,180,252,.9)] group-[.is-unread]:block"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-[#c3c9d9] no-underline hover:bg-[#1a1f2e] [&.is-unread]:bg-[#818cf8]/8 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] [&_b]:font-[650] [&_b]:text-[#f1f5f9] is-unread" href="#"><span class="grid size-9 flex-none place-items-center rounded-full text-xs font-bold bg-[#fbbf24]/12 text-[#fbbf24]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-cpu"/></svg></span><span>CPU on <b>worker-eu-3</b> above 90% for 10 minutes<span class="mt-1 block text-xs text-[#8b93a7]">18 min ago</span></span><span class="absolute top-1/2 right-3 -mt-1 hidden size-2 rounded-full bg-[#a5b4fc] shadow-[0_0_0_3px_rgba(129,140,248,.2),0_0_12px_rgba(165,180,252,.9)] group-[.is-unread]:block"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-[#c3c9d9] no-underline hover:bg-[#1a1f2e] [&.is-unread]:bg-[#818cf8]/8 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] [&_b]:font-[650] [&_b]:text-[#f1f5f9] is-unread" href="#"><span class="grid size-9 flex-none place-items-center rounded-full text-xs font-bold bg-[#312e81] text-[#c7d2fe]" aria-hidden="true">YN</span><span><b>Yuki Nakamura</b> requested your review on <b>#482 Rate limit per token</b><span class="mt-1 block text-xs text-[#8b93a7]">42 min ago</span></span><span class="absolute top-1/2 right-3 -mt-1 hidden size-2 rounded-full bg-[#a5b4fc] shadow-[0_0_0_3px_rgba(129,140,248,.2),0_0_12px_rgba(165,180,252,.9)] group-[.is-unread]:block"></span><span class="sr-only">(unread)</span></a></li>
          <li><a class="nf-item group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-[#c3c9d9] no-underline hover:bg-[#1a1f2e] [&.is-unread]:bg-[#818cf8]/8 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] [&_b]:font-[650] [&_b]:text-[#f1f5f9]" href="#"><span class="grid size-9 flex-none place-items-center rounded-full text-xs font-bold bg-[#164e63] text-[#a5f3fc]" aria-hidden="true">OB</span><span><b>Omar Bakr</b> merged <b>#479 Retry webhooks with backoff</b><span class="mt-1 block text-xs text-[#8b93a7]">2 h ago</span></span><span class="absolute top-1/2 right-3 -mt-1 hidden size-2 rounded-full bg-[#a5b4fc] shadow-[0_0_0_3px_rgba(129,140,248,.2),0_0_12px_rgba(165,180,252,.9)] group-[.is-unread]:block"></span></a></li>
          <li><a class="nf-item group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-[#c3c9d9] no-underline hover:bg-[#1a1f2e] [&.is-unread]:bg-[#818cf8]/8 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#a5b4fc] [&_b]:font-[650] [&_b]:text-[#f1f5f9]" href="#"><span class="grid size-9 flex-none place-items-center rounded-full text-xs font-bold bg-[#4ade80]/12 text-[#4ade80]" aria-hidden="true"><svg class="size-[18px]"><use href="#i-ok"/></svg></span><span>Nightly backup of <b>orbit-prod</b> finished: 14.2 GB<span class="mt-1 block text-xs text-[#8b93a7]">6 h ago</span></span><span class="absolute top-1/2 right-3 -mt-1 hidden size-2 rounded-full bg-[#a5b4fc] shadow-[0_0_0_3px_rgba(129,140,248,.2),0_0_12px_rgba(165,180,252,.9)] group-[.is-unread]:block"></span></a></li>
        </ul>
        <div class="flex items-center justify-between border-t border-[#262c3d] pt-2.5 pr-3 pb-3 pl-[18px] text-xs text-[#8b93a7]"><a class="py-1 text-sm font-semibold text-[#c7d2fe] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Open inbox</a><span>Synced just now</span></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
  </header>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#8b93a7] uppercase">Toasts with status glow and a log line</span>
<div class="flex max-w-[420px] flex-col gap-3 rounded-[18px] border border-[#262c3d] bg-[#0b0d14] p-[22px] max-[520px]:p-3.5" role="status" aria-live="polite" aria-atomic="false" aria-label="Notifications" data-dt-stack>
    <div class="relative flex animate-dk-in items-start gap-3 overflow-hidden rounded-[14px] border border-[#262c3d] bg-[#131722] bg-[image:linear-gradient(90deg,color-mix(in_srgb,var(--dk-tone)_10%,transparent),transparent_45%)] py-3.5 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#c3c9d9] shadow-[0_16px_36px_-16px_rgba(0,0,0,.8)] [--dk-tone:#4ade80] before:absolute before:top-3 before:bottom-3 before:left-0 before:w-[3px] before:rounded-r-[3px] before:bg-(--dk-tone) before:shadow-[0_0_12px_var(--dk-tone)] motion-reduce:animate-none" data-dt>
      <span class="grid size-[30px] flex-none place-items-center rounded-full bg-[color-mix(in_srgb,var(--dk-tone)_14%,transparent)] text-(--dk-tone)" aria-hidden="true"><svg class="size-4"><use href="#i-ok"/></svg></span>
      <div class="min-w-0 flex-1"><p class="m-0 text-[.9375rem] font-[650] text-[#f1f5f9]">Deploy finished</p><p class="mt-0.5 mb-0">web-app v5.2.0 is live on orbit.dev.</p><a class="mt-2 inline-block font-[650] text-[#c7d2fe] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">View deploy</a></div>
      <button class="grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg border-0 bg-transparent p-0 text-[#8b93a7] hover:bg-[#1a1f2e] hover:text-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" aria-label="Dismiss: Deploy finished" data-dt-close><svg class="size-3.5" aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="relative flex animate-dk-in items-start gap-3 overflow-hidden rounded-[14px] border border-[#262c3d] bg-[#131722] bg-[image:linear-gradient(90deg,color-mix(in_srgb,var(--dk-tone)_10%,transparent),transparent_45%)] py-3.5 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#c3c9d9] shadow-[0_16px_36px_-16px_rgba(0,0,0,.8)] [--dk-tone:#fbbf24] before:absolute before:top-3 before:bottom-3 before:left-0 before:w-[3px] before:rounded-r-[3px] before:bg-(--dk-tone) before:shadow-[0_0_12px_var(--dk-tone)] motion-reduce:animate-none" data-dt>
      <span class="grid size-[30px] flex-none place-items-center rounded-full bg-[color-mix(in_srgb,var(--dk-tone)_14%,transparent)] text-(--dk-tone)" aria-hidden="true"><svg class="size-4"><use href="#i-warn"/></svg></span>
      <div class="min-w-0 flex-1"><p class="m-0 text-[.9375rem] font-[650] text-[#f1f5f9]">High memory on cache-2</p><p class="mt-0.5 mb-0">Using 7.4 of 8 GB. Eviction starts at 95%.</p></div>
      <button class="grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg border-0 bg-transparent p-0 text-[#8b93a7] hover:bg-[#1a1f2e] hover:text-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" aria-label="Dismiss: High memory on cache-2" data-dt-close><svg class="size-3.5" aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="relative flex animate-dk-in items-start gap-3 overflow-hidden rounded-[14px] border border-[#262c3d] bg-[#131722] bg-[image:linear-gradient(90deg,color-mix(in_srgb,var(--dk-tone)_10%,transparent),transparent_45%)] py-3.5 pr-2.5 pl-3.5 text-sm leading-[1.45] text-[#c3c9d9] shadow-[0_16px_36px_-16px_rgba(0,0,0,.8)] [--dk-tone:#fb7185] before:absolute before:top-3 before:bottom-3 before:left-0 before:w-[3px] before:rounded-r-[3px] before:bg-(--dk-tone) before:shadow-[0_0_12px_var(--dk-tone)] motion-reduce:animate-none" role="alert" data-dt>
      <span class="grid size-[30px] flex-none place-items-center rounded-full bg-[color-mix(in_srgb,var(--dk-tone)_14%,transparent)] text-(--dk-tone)" aria-hidden="true"><svg class="size-4"><use href="#i-error"/></svg></span>
      <div class="min-w-0 flex-1"><p class="m-0 text-[.9375rem] font-[650] text-[#f1f5f9]">Build #1294 failed</p><p class="mt-0.5 mb-0">Step “test” exited with code 1.</p><code class="mt-2 block rounded-lg border border-[#262c3d] bg-[#05070c] px-2.5 py-2 font-mono text-xs leading-normal font-medium [overflow-wrap:anywhere] whitespace-pre-wrap text-[#fda4af]">FAIL src/billing/invoice.test.ts › rounds tax to 2 places</code><a class="mt-2 inline-block font-[650] text-[#c7d2fe] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" href="#">Open logs</a></div>
      <button class="grid size-[30px] flex-none cursor-pointer place-items-center rounded-lg border-0 bg-transparent p-0 text-[#8b93a7] hover:bg-[#1a1f2e] hover:text-[#f1f5f9] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#a5b4fc]" type="button" aria-label="Dismiss: Build #1294 failed" data-dt-close><svg class="size-3.5" aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
</div></div>
CSS
@theme {
  --animate-dk-in: dk-in .25s cubic-bezier(.2,.8,.2,1);
  @keyframes dk-in { from { opacity: 0; transform: translateY(-6px); } }
}
JavaScript
(function () {
  // Bell panel: same pattern as a light dropdown. Open/close, Esc, outside click, mark read.
    document.querySelectorAll('[data-nf]').forEach(function (nf) {
      var bell = nf.querySelector('[data-nf-bell]');
      var panel = nf.querySelector('[data-nf-panel]');
      if (!bell || !panel) return;
      var count = nf.querySelector('[data-nf-count]');
      var badge = nf.querySelector('[data-nf-new]');
      var markAll = nf.querySelector('[data-nf-markall]');
      var status = nf.querySelector('[data-nf-status]');

      function setOpen(open) {
        bell.setAttribute('aria-expanded', open);
        panel.hidden = !open;
      }
      function refresh() {
        var n = panel.querySelectorAll('.is-unread').length;
        if (count) {
          count.hidden = n === 0;
          if (count.firstChild && count.firstChild.nodeType === 3) count.firstChild.nodeValue = n;
        }
        if (badge) { badge.hidden = n === 0; badge.textContent = n + ' new'; }
        if (markAll) markAll.disabled = n === 0;
      }
      function read(item) {
        item.classList.remove('is-unread');
        var sr = item.querySelector('.sr-only');
        if (sr) sr.remove();
      }

      bell.addEventListener('click', function () { setOpen(panel.hidden); });
      markAll && markAll.addEventListener('click', function () {
        panel.querySelectorAll('.is-unread').forEach(read);
        refresh();
        if (status) status.textContent = 'All notifications marked as read';
      });
      panel.addEventListener('click', function (e) {
        var item = e.target.closest('.nf-item');
        if (!item) return;
        e.preventDefault(); // demo links; remove when they point somewhere
        read(item); refresh();
      });
      // Esc closes and returns focus to the bell; a click outside closes.
      nf.addEventListener('keydown', function (e) {
        if (e.key === 'Escape' && !panel.hidden) { setOpen(false); bell.focus(); }
      });
      document.addEventListener('click', function (e) {
        if (!panel.hidden && !nf.contains(e.target)) setOpen(false);
      });
      refresh();
    });

  // Toasts: the close button removes the toast and moves focus to the next one.
  document.querySelectorAll('[data-dt-stack]').forEach(function (stack) {
    stack.addEventListener('click', function (e) {
      var btn = e.target.closest('[data-dt-close]');
      if (!btn) return;
      var toast = btn.closest('[data-dt]');
      var next = toast.nextElementSibling || toast.previousElementSibling;
      toast.remove();
      var target = next && next.querySelector('[data-dt-close]');
      if (target) target.focus();
    });
  });
})();

About this set

Notification UI for dark dashboards and developer tools. A bell on a near-black app bar has a red count with a soft glow and opens a panel of five events from a deployment platform: a production deploy, a CPU alert, a review request, a merged pull request and a finished backup, with unread items tinted and marked by a glowing dot, a Mark all as read button and an Open inbox link. Beside it, a stack of three toasts shows a finished deploy with a View deploy link, a memory warning and a failed build with the failing test in a monospace log line and an Open logs link. Each toast has a coloured stripe with a glow and a faint wash of its status colour mixed in with color-mix(), so the status reads at a glance without lowering text contrast. Every text colour was checked for 4.5:1 against its surface. Closing a toast moves focus to the next one. Use it for monitoring, CI and admin interfaces with a dark theme. The Bootstrap version runs under data-bs-theme=”dark” with the Dropdown and Toast plugins.

What’s included

  • Near-black palette with every text colour checked for 4.5:1
  • Status stripes and glows from one --dk-tone custom property
  • Failed build toast with a wrapped log line
  • Bell panel with count glow, unread dots and mark all as read
  • Bootstrap in data-bs-theme="dark" with .dropdown and .toast

Accessibility

The bell's name includes the unread count and it has aria-expanded and aria-controls; Esc closes the panel and returns focus. Unread items carry visually hidden (unread) text and Mark all as read is announced. The toast stack is a polite live region and the failed build uses role="alert"; each close button is named after its toast and closing moves focus to the next toast. Focus outlines use a light indigo that stands out on the dark surfaces, and animations are off under prefers-reduced-motion.

Customise it

In plain CSS change --dk-bg, --dk-surface, --dk-line, --dk-ink, --dk-text and --dk-focus on .dk-frame and .dk-stack, and --dk-tone on each toast status. In Bootstrap the same variables feed the --bs-dropdown-* and --bs-toast-* variables; in Tailwind edit the hex values and the [--dk-tone:…] utility.