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-comment" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h16v12H8l-4 4z"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0z"/></symbol>
  <symbol id="i-share" viewBox="0 0 24 24" fill="currentColor"><path d="M14 4l7 7-7 7v-4c-5 0-8 1.5-10 5 .5-6 3.5-10 10-11z"/></symbol>
  <symbol id="i-cash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="19" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/></symbol>
  <symbol id="i-clock" 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 7v5l3 2"/></symbol>
  <symbol id="i-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
</svg>

<div><span class="lbl">Count badge, grouped Today and Earlier, mark all as read</span>
<div class="frame">
  <header class="nf-topbar">
    <a class="nf-logo" href="#"><i aria-hidden="true">L</i>Ledgerly</a>
    <nav class="nf-links" aria-label="Main"><a href="#" aria-current="page">Overview</a><a href="#">Invoices</a><a href="#">Reports</a></nav>
    <div class="nf" data-nf>
      <button class="nf-bell" type="button" aria-expanded="true" aria-controls="nf-panel" data-nf-bell>
        <svg aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="nf-count" data-nf-count>3<span class="sr-only"> unread</span></span>
      </button>
      <section class="nf-panel" id="nf-panel" aria-labelledby="nf-title" data-nf-panel>
        <div class="nf-head"><h2 id="nf-title">Notifications</h2><span class="nf-new" data-nf-new>3 new</span>
          <button class="nf-markall" type="button" data-nf-markall><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <div class="nf-scroll">
          <h3 class="nf-group">Today</h3>
          <ul class="nf-list">
            <li><a class="nf-item is-unread" href="#"><span class="nf-av c1" aria-hidden="true">AM<i><svg><use href="#i-comment"/></svg></i></span>
              <span><b>Amara Mensah</b> commented on <b>Q3 budget review</b><q>Can we move the travel line to Operations?</q><span class="nf-time">12 minutes ago</span></span><span class="nf-unread"></span><span class="sr-only">(unread)</span></a></li>
            <li><a class="nf-item is-unread" href="#"><span class="nf-av ok" aria-hidden="true"><svg><use href="#i-cash"/></svg></span>
              <span>Payout of <b>$4,820.00</b> sent to your account ending 0921<span class="nf-time">1 hour ago</span></span><span class="nf-unread"></span><span class="sr-only">(unread)</span></a></li>
            <li><a class="nf-item is-unread" href="#"><span class="nf-av c2" aria-hidden="true">JT<i><svg><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="nf-time">3 hours ago</span></span><span class="nf-unread"></span><span class="sr-only">(unread)</span></a></li>
          </ul>
          <h3 class="nf-group">Earlier</h3>
          <ul class="nf-list">
            <li><a class="nf-item" href="#"><span class="nf-av warn" aria-hidden="true"><svg><use href="#i-clock"/></svg></span>
              <span>Your <b>Pro trial</b> ends in 3 days. Add a card to keep your reports.<span class="nf-time">Yesterday</span></span><span class="nf-unread"></span></a></li>
            <li><a class="nf-item" href="#"><span class="nf-av c3" aria-hidden="true">SL<i><svg><use href="#i-share"/></svg></i></span>
              <span><b>Sofia Lindqvist</b> shared <b>Vendor list 2026</b> with you<span class="nf-time">Monday</span></span><span class="nf-unread"></span></a></li>
          </ul>
        </div>
        <div class="nf-foot"><a class="nf-link" href="#">View all notifications</a><a class="nf-link icon" href="#" aria-label="Notification settings"><svg aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
    <span class="nf-me" aria-hidden="true">EV</span>
  </header>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
</div></div>

<div><span class="lbl">Dot badge on a side rail, compact panel</span>
<div class="frame rail">
  <nav class="rail-nav" aria-label="Workspace">
    <span class="rail-logo" aria-hidden="true">L</span>
    <div class="nf" data-nf>
      <button class="nf-bell" type="button" aria-expanded="false" aria-controls="nf-panel-2" data-nf-bell>
        <svg aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="nf-dot" data-nf-count><span class="sr-only">, new</span></span>
      </button>
      <section class="nf-panel is-side" id="nf-panel-2" aria-labelledby="nf-title-2" hidden data-nf-panel>
        <div class="nf-head"><h2 id="nf-title-2">Notifications</h2>
          <button class="nf-markall" type="button" data-nf-markall><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <div class="nf-scroll">
          <h3 class="nf-group">Today</h3>
          <ul class="nf-list">
            <li><a class="nf-item is-unread" href="#"><span class="nf-av c2" aria-hidden="true">JT<i><svg><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="nf-time">3 hours ago</span></span><span class="nf-unread"></span><span class="sr-only">(unread)</span></a></li>
          </ul>
          <h3 class="nf-group">Earlier</h3>
          <ul class="nf-list">
            <li><a class="nf-item" href="#"><span class="nf-av ok" aria-hidden="true"><svg><use href="#i-cash"/></svg></span>
              <span>March statement is ready to download<span class="nf-time">Friday</span></span><span class="nf-unread"></span></a></li>
          </ul>
        </div>
        <div class="nf-foot"><a class="nf-link" href="#">View all</a><a class="nf-link icon" href="#" aria-label="Notification settings"><svg aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
  </nav>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i></div>
</div></div>
CSS
/* Notification bell + dropdown panel. The count is real text inside the button, followed by visually hidden
   "unread", so the button reads "Notifications 3 unread". The panel is a labelled region opened by the button. */
.nf-topbar{--nf-brand:#4f46e5;--nf-ink:#0f172a;--nf-text:#475569;--nf-muted:#5b6474;--nf-line:#e2e8f0;
  position:relative;display:flex;align-items:center;gap:20px;height:64px;padding:0 20px;background:#fff;border-bottom:1px solid var(--nf-line);border-radius:18px 18px 0 0}
.nf-logo{display:flex;align-items:center;gap:9px;font-weight:750;color:var(--nf-ink);text-decoration:none}
.nf-logo i{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--nf-brand);color:#fff;font-style:normal;font-size:.9rem}
.nf-links{display:flex;gap:4px;margin-right:auto}
.nf-links a{padding:7px 11px;border-radius:8px;color:var(--nf-text);font-size:.9rem;font-weight:550;text-decoration:none}
.nf-links a[aria-current]{color:var(--nf-ink);background:#f1f5f9}
.nf-me{display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:#fde68a;color:#78350f;font-size:.8rem;font-weight:700}
.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}

.nf{--nf-brand:#4f46e5;--nf-ink:#0f172a;--nf-text:#475569;--nf-muted:#5b6474;--nf-line:#e2e8f0;position:relative}
.nf-bell{position:relative;display:grid;place-items:center;width:42px;height:42px;padding:0;border:1px solid transparent;border-radius:12px;background:transparent;color:#334155;cursor:pointer}
.nf-bell:hover,.nf-bell[aria-expanded="true"]{background:#eef2ff;color:var(--nf-brand)}
.nf-bell svg{width:22px;height:22px}
.nf-count{position:absolute;top:3px;right:2px;min-width:19px;height:19px;padding:0 5px;border:2px solid #fff;border-radius:999px;background:#dc2626;color:#fff;
  font-size:.6875rem;font-weight:700;line-height:15px;text-align:center}
.nf-count[hidden],.nf-dot[hidden]{display:none}
.nf-dot{position:absolute;top:8px;right:9px;width:10px;height:10px;border:2px solid #fff;border-radius:50%;background:#dc2626}
.nf-logo:focus-visible,.nf-links a:focus-visible,.nf-bell:focus-visible,.nf-link:focus-visible,.nf-markall:focus-visible,.nf-item:focus-visible{outline:2px solid var(--nf-brand);outline-offset:2px}
.nf-item:focus-visible{outline-offset:-2px}

.nf-panel{position:absolute;top:calc(100% + 10px);right:-8px;z-index:20;width:400px;max-width:calc(100vw - 32px);background:#fff;border:1px solid var(--nf-line);border-radius:16px;
  box-shadow:0 28px 56px -24px rgba(15,23,42,.35),0 4px 12px rgba(15,23,42,.06);transform-origin:top right;animation:nf-in .18s ease-out}
.nf-panel[hidden]{display:none}
.nf-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px;padding:16px 18px 12px}
.nf-head h2{margin:0;font-size:1rem;color:var(--nf-ink)}
.nf-new{white-space:nowrap;padding:2px 8px;border-radius:999px;background:#eef2ff;color:#4338ca;font-size:.75rem;font-weight:650}
.nf-new[hidden]{display:none}
.nf-markall{white-space:nowrap;display:inline-flex;align-items:center;gap:6px;margin-left:auto;padding:5px 8px;border:0;border-radius:8px;background:transparent;color:#4338ca;font:inherit;font-size:.8125rem;font-weight:600;cursor:pointer}
.nf-markall:hover{background:#eef2ff}
.nf-markall:disabled{color:#64748b;background:transparent;cursor:default}
.nf-markall svg{width:16px;height:16px}
.nf-scroll{max-height:420px;overflow-y:auto;border-top:1px solid var(--nf-line)}
.nf-group{margin:0;padding:12px 18px 6px;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--nf-muted)}
.nf-list{margin:0;padding:0 8px 6px;list-style:none}
.nf-list li+li{margin-top:2px}
.nf-item{position:relative;display:flex;gap:12px;padding:11px 30px 11px 10px;border-radius:12px;color:var(--nf-text);font-size:.875rem;line-height:1.45;text-decoration:none}
.nf-item:hover{background:#f8fafc}
.nf-item.is-unread{background:#f5f7ff}
.nf-item.is-unread:hover{background:#eef2ff}
.nf-item b{color:var(--nf-ink);font-weight:650}
.nf-item q{display:block;margin-top:4px;padding-left:9px;border-left:2px solid #c7d2fe;color:var(--nf-text);quotes:none}
.nf-time{display:block;margin-top:4px;font-size:.75rem;color:var(--nf-muted)}
.nf-av{position:relative;flex:none;display:grid;place-items:center;width:38px;height:38px;border-radius:50%;font-size:.8rem;font-weight:700}
.nf-av>svg{width:19px;height:19px}
.nf-av i{position:absolute;right:-3px;bottom:-3px;display:grid;place-items:center;width:18px;height:18px;border:2px solid #fff;border-radius:50%;background:var(--nf-brand);color:#fff}
.nf-av i svg{width:10px;height:10px}
.nf-av.c1{background:#ddd6fe;color:#4c1d95}.nf-av.c2{background:#bfdbfe;color:#1e3a8a}.nf-av.c3{background:#fecdd3;color:#881337}
.nf-av.ok{background:#dcfce7;color:#15803d}.nf-av.warn{background:#fef3c7;color:#b45309}
.nf-unread{position:absolute;top:50%;right:12px;width:9px;height:9px;margin-top:-4.5px;border-radius:50%;background:var(--nf-brand)}
.nf-item:not(.is-unread) .nf-unread{display:none}
.nf-foot{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-top:1px solid var(--nf-line)}
.nf-link{padding:6px 8px;border-radius:8px;color:#4338ca;font-size:.875rem;font-weight:600;text-decoration:none}
.nf-link:hover{background:#eef2ff}
.nf-link.icon{display:grid;place-items:center;width:34px;height:34px;padding:0;color:#475569}
.nf-link.icon svg{width:18px;height:18px}
/* Compact panel opening to the right of a side rail */
.nf-panel.is-side{top:auto;right:auto;bottom:-6px;left:calc(100% + 14px);width:340px;transform-origin:bottom left}
.is-side .nf-scroll{max-height:300px}
@keyframes nf-in{from{opacity:0;transform:translateY(-6px) scale(.98)}}
@media (max-width:560px){
  .nf-links{display:none}.nf-logo{margin-right:auto}
  .nf-topbar .nf{position:static}
  .nf-topbar .nf-panel{left:8px;right:8px;width:auto;max-width:none}
  .nf-panel.is-side{left:calc(100% + 8px);width:min(340px,calc(100vw - 120px))}
}
@media (prefers-reduced-motion:reduce){.nf-panel{animation:none}}
JavaScript
(function () {
  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();
  });
})();

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-comment" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h16v12H8l-4 4z"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0z"/></symbol>
  <symbol id="i-share" viewBox="0 0 24 24" fill="currentColor"><path d="M14 4l7 7-7 7v-4c-5 0-8 1.5-10 5 .5-6 3.5-10 10-11z"/></symbol>
  <symbol id="i-cash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="19" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/></symbol>
  <symbol id="i-clock" 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 7v5l3 2"/></symbol>
  <symbol id="i-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
</svg>

<div><span class="lbl">Count badge, grouped Today and Earlier, mark all as read</span>
<div class="frame">
  <header class="nf-topbar d-flex align-items-center gap-3">
    <a class="nf-logo d-flex align-items-center gap-2 text-decoration-none rounded-2" href="#"><i aria-hidden="true">L</i>Ledgerly</a>
    <nav aria-label="Main" class="me-auto"><ul class="nav nav-pills nf-links gap-1"><li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Overview</a></li><li class="nav-item"><a class="nav-link" href="#">Invoices</a></li><li class="nav-item"><a class="nav-link" href="#">Reports</a></li></ul></nav>
    <div class="dropdown nf" data-nf>
      <button class="btn nf-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 text-bg-danger nf-count" data-nf-count>3<span class="visually-hidden"> unread</span></span>
      </button>
      <section class="dropdown-menu dropdown-menu-end nf-panel show" data-bs-popper="static" aria-labelledby="nf-title">
        <div class="nf-head d-flex flex-wrap align-items-center gap-2"><h2 id="nf-title">Notifications</h2><span class="badge rounded-pill nf-new" data-nf-new>3 new</span>
          <button class="btn nf-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>
        <div class="nf-scroll">
          <h3 class="dropdown-header nf-group">Today</h3>
          <ul class="list-unstyled mb-0 nf-list">
            <li><a class="dropdown-item nf-item is-unread" href="#"><span class="nf-av c1" aria-hidden="true">AM<i><svg><use href="#i-comment"/></svg></i></span>
              <span><b>Amara Mensah</b> commented on <b>Q3 budget review</b><q>Can we move the travel line to Operations?</q><span class="nf-time">12 minutes ago</span></span><span class="nf-unread"></span><span class="visually-hidden">(unread)</span></a></li>
            <li><a class="dropdown-item nf-item is-unread" href="#"><span class="nf-av ok" aria-hidden="true"><svg><use href="#i-cash"/></svg></span>
              <span>Payout of <b>$4,820.00</b> sent to your account ending 0921<span class="nf-time">1 hour ago</span></span><span class="nf-unread"></span><span class="visually-hidden">(unread)</span></a></li>
            <li><a class="dropdown-item nf-item is-unread" href="#"><span class="nf-av c2" aria-hidden="true">JT<i><svg><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="nf-time">3 hours ago</span></span><span class="nf-unread"></span><span class="visually-hidden">(unread)</span></a></li>
          </ul>
          <h3 class="dropdown-header nf-group">Earlier</h3>
          <ul class="list-unstyled mb-0 nf-list">
            <li><a class="dropdown-item nf-item" href="#"><span class="nf-av warn" aria-hidden="true"><svg><use href="#i-clock"/></svg></span>
              <span>Your <b>Pro trial</b> ends in 3 days. Add a card to keep your reports.<span class="nf-time">Yesterday</span></span><span class="nf-unread"></span></a></li>
            <li><a class="dropdown-item nf-item" href="#"><span class="nf-av c3" aria-hidden="true">SL<i><svg><use href="#i-share"/></svg></i></span>
              <span><b>Sofia Lindqvist</b> shared <b>Vendor list 2026</b> with you<span class="nf-time">Monday</span></span><span class="nf-unread"></span></a></li>
          </ul>
        </div>
        <div class="nf-foot d-flex justify-content-between align-items-center"><a class="nf-link" href="#">View all notifications</a><a class="nf-link icon" href="#" aria-label="Notification settings"><svg aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="visually-hidden" role="status" data-nf-status></p>
      </section>
    </div>
    <span class="nf-me" aria-hidden="true">EV</span>
  </header>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
</div></div>

<div><span class="lbl">Dot badge on a side rail, compact panel</span>
<div class="frame rail">
  <nav class="rail-nav" aria-label="Workspace">
    <span class="rail-logo" aria-hidden="true">L</span>
    <div class="dropend nf" data-nf>
      <button class="btn nf-bell" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
        <svg aria-hidden="true"><use href="#i-bell"/></svg><span class="visually-hidden">Notifications</span>
        <span class="nf-dot" data-nf-count><span class="visually-hidden">, new</span></span>
      </button>
      <section class="dropdown-menu nf-panel is-side" aria-labelledby="nf-title-2">
        <div class="nf-head d-flex flex-wrap align-items-center gap-2"><h2 id="nf-title-2">Notifications</h2>
          <button class="btn nf-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>
        <div class="nf-scroll">
          <h3 class="dropdown-header nf-group">Today</h3>
          <ul class="list-unstyled mb-0 nf-list">
            <li><a class="dropdown-item nf-item is-unread" href="#"><span class="nf-av c2" aria-hidden="true">JT<i><svg><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="nf-time">3 hours ago</span></span><span class="nf-unread"></span><span class="visually-hidden">(unread)</span></a></li>
          </ul>
          <h3 class="dropdown-header nf-group">Earlier</h3>
          <ul class="list-unstyled mb-0 nf-list">
            <li><a class="dropdown-item nf-item" href="#"><span class="nf-av ok" aria-hidden="true"><svg><use href="#i-cash"/></svg></span>
              <span>March statement is ready to download<span class="nf-time">Friday</span></span><span class="nf-unread"></span></a></li>
          </ul>
        </div>
        <div class="nf-foot d-flex justify-content-between align-items-center"><a class="nf-link" href="#">View all</a><a class="nf-link icon" href="#" aria-label="Notification settings"><svg aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="visually-hidden" role="status" data-nf-status></p>
      </section>
    </div>
  </nav>
  <div class="frame-rows" aria-hidden="true"><i></i><i></i><i></i></div>
</div></div>
CSS
/* Bootstrap .dropdown with a .badge count. The dropdown plugin handles open/close, Esc, outside clicks and arrow keys
   between .dropdown-item links; the small script below only marks notifications as read. */
.nf-topbar,.nf{--nf-brand:#4f46e5;--nf-ink:#0f172a;--nf-text:#475569;--nf-muted:#5b6474;--nf-line:#e2e8f0}
.nf-topbar{position:relative;height:64px;padding:0 20px;background:#fff;border-bottom:1px solid var(--nf-line);border-radius:18px 18px 0 0}
.nf-logo{font-weight:750;color:var(--nf-ink)}
.nf-logo i{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--nf-brand);color:#fff;font-style:normal;font-size:.9rem}
.nf-links{--bs-nav-link-padding-x:11px;--bs-nav-link-padding-y:7px;--bs-nav-link-color:var(--nf-text);--bs-nav-link-hover-color:var(--nf-ink);--bs-nav-link-font-size:.9rem;--bs-nav-link-font-weight:550;
  --bs-nav-pills-border-radius:8px;--bs-nav-pills-link-active-bg:#f1f5f9;--bs-nav-pills-link-active-color:var(--nf-ink)}
.nf-me{display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:#fde68a;color:#78350f;font-size:.8rem;font-weight:700}
.nf-bell{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-color:#334155;--bs-btn-hover-color:var(--nf-brand);--bs-btn-hover-bg:#eef2ff;--bs-btn-active-bg:#eef2ff;--bs-btn-active-color:var(--nf-brand);
  --bs-btn-border-color:transparent;--bs-btn-hover-border-color:transparent;--bs-btn-active-border-color:transparent;--bs-btn-border-radius:12px;--bs-btn-focus-box-shadow:0 0 0 2px var(--nf-brand);
  position:relative;display:grid;place-items:center;width:42px;height:42px}
.nf-bell.show{background:#eef2ff;color:var(--nf-brand)}
.nf-bell svg{width:22px;height:22px}
.nf-bell .nf-count{--bs-badge-padding-x:5px;--bs-badge-padding-y:0;--bs-badge-font-size:.6875rem;position:absolute;top:3px;right:2px;min-width:19px;height:19px;border:2px solid #fff;line-height:15px}
.nf-dot{position:absolute;top:8px;right:9px;width:10px;height:10px;border:2px solid #fff;border-radius:50%;background:#dc2626}
.nf-logo:focus-visible,.nf-links .nav-link:focus-visible,.nf-link:focus-visible,.nf-markall:focus-visible{outline:2px solid var(--nf-brand);outline-offset:2px;box-shadow:none}
.nf-panel{--bs-dropdown-padding-y:0;--bs-dropdown-border-color:var(--nf-line);--bs-dropdown-border-radius:16px;--bs-dropdown-spacer:10px;--bs-dropdown-zindex:20;
  --bs-dropdown-box-shadow:0 28px 56px -24px rgba(15,23,42,.35),0 4px 12px rgba(15,23,42,.06);--bs-dropdown-link-color:var(--nf-text);--bs-dropdown-link-hover-bg:#f8fafc;
  --bs-dropdown-link-active-bg:#eef2ff;--bs-dropdown-link-active-color:var(--nf-text);--bs-dropdown-header-color:var(--nf-muted);
  width:400px;max-width:calc(100vw - 32px);box-shadow:var(--bs-dropdown-box-shadow)}
.nf-panel.show{animation:nf-in .18s ease-out}
.nf-panel.dropdown-menu-end[data-bs-popper]{right:-8px}
.nf-head{padding:16px 18px 12px}
.nf-head h2{margin:0;font-size:1rem;font-weight:700;color:var(--nf-ink)}
.nf-new{--bs-badge-font-size:.75rem;--bs-badge-font-weight:650;--bs-badge-color:#4338ca;background:#eef2ff}
.nf-markall{--bs-btn-padding-x:8px;--bs-btn-padding-y:5px;--bs-btn-font-size:.8125rem;--bs-btn-font-weight:600;--bs-btn-color:#4338ca;--bs-btn-hover-color:#4338ca;--bs-btn-hover-bg:#eef2ff;
  --bs-btn-active-color:#4338ca;--bs-btn-disabled-color:#64748b;--bs-btn-border-radius:8px;--bs-btn-border-width:0;text-decoration:none;white-space:nowrap}
.nf-markall svg{width:16px;height:16px}
.nf-scroll{max-height:420px;overflow-y:auto;border-top:1px solid var(--nf-line)}
.nf-group{padding:12px 18px 6px;font-size:.6875rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.nf-list{padding:0 8px 6px}
.nf-list li+li{margin-top:2px}
.nf-item{position:relative;display:flex;gap:12px;padding:11px 30px 11px 10px;border-radius:12px;font-size:.875rem;line-height:1.45;white-space:normal}
.nf-item:focus-visible{outline:2px solid var(--nf-brand);outline-offset:-2px}
.nf-item.is-unread{background:#f5f7ff}
.nf-item.is-unread:hover{background:#eef2ff}
.nf-item b{color:var(--nf-ink);font-weight:650}
.nf-item q{display:block;margin-top:4px;padding-left:9px;border-left:2px solid #c7d2fe;quotes:none}
.nf-time{display:block;margin-top:4px;font-size:.75rem;color:var(--nf-muted)}
.nf-av{position:relative;flex:none;display:grid;place-items:center;width:38px;height:38px;border-radius:50%;font-size:.8rem;font-weight:700}
.nf-av>svg{width:19px;height:19px}
.nf-av i{position:absolute;right:-3px;bottom:-3px;display:grid;place-items:center;width:18px;height:18px;border:2px solid #fff;border-radius:50%;background:var(--nf-brand);color:#fff}
.nf-av i svg{width:10px;height:10px}
.nf-av.c1{background:#ddd6fe;color:#4c1d95}.nf-av.c2{background:#bfdbfe;color:#1e3a8a}.nf-av.c3{background:#fecdd3;color:#881337}
.nf-av.ok{background:#dcfce7;color:#15803d}.nf-av.warn{background:#fef3c7;color:#b45309}
.nf-unread{position:absolute;top:50%;right:12px;width:9px;height:9px;margin-top:-4.5px;border-radius:50%;background:var(--nf-brand)}
.nf-item:not(.is-unread) .nf-unread{display:none}
.nf-foot{padding:10px 12px;border-top:1px solid var(--nf-line)}
.nf-link{padding:6px 8px;border-radius:8px;color:#4338ca;font-size:.875rem;font-weight:600;text-decoration:none}
.nf-link:hover{background:#eef2ff;color:#4338ca}
.nf-link.icon{display:grid;place-items:center;width:34px;height:34px;padding:0;color:#475569}
.nf-link.icon svg{width:18px;height:18px}
.nf-panel.is-side{width:340px}
.is-side .nf-scroll{max-height:300px}
@keyframes nf-in{from{opacity:0;transform:translateY(-6px) scale(.98)}}
@media (max-width:560px){
  .nf-links{display:none!important}
  .nf-topbar .dropdown{position:static}
  .nf-topbar .nf-panel[data-bs-popper]{left:8px;right:8px;width:auto;max-width:none}
  .nf-panel.is-side{width:min(340px,calc(100vw - 120px))}
}
@media (prefers-reduced-motion:reduce){.nf-panel.show{animation:none}}
JavaScript
(function () {
  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();
  });
})();

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-comment" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h16v12H8l-4 4z"/></symbol>
  <symbol id="i-user" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0z"/></symbol>
  <symbol id="i-share" viewBox="0 0 24 24" fill="currentColor"><path d="M14 4l7 7-7 7v-4c-5 0-8 1.5-10 5 .5-6 3.5-10 10-11z"/></symbol>
  <symbol id="i-cash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="6" width="19" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/></symbol>
  <symbol id="i-clock" 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 7v5l3 2"/></symbol>
  <symbol id="i-gear" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Count badge, grouped Today and Earlier, mark all as read</span>
<div class="relative h-[640px] rounded-[18px] border border-[#d6dce5] bg-[#f8fafc] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-28px_rgba(15,23,42,.3)] max-[600px]:h-[700px]">
  <header class="relative flex h-16 items-center gap-5 rounded-t-[18px] border-b border-[#e2e8f0] bg-white px-5">
    <a class="flex items-center gap-[9px] font-[750] text-[#0f172a] no-underline max-[560px]:mr-auto focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><i class="grid size-[30px] place-items-center rounded-[9px] bg-[#4f46e5] text-[.9rem] text-white not-italic" aria-hidden="true">L</i>Ledgerly</a>
    <nav class="mr-auto flex gap-1 max-[560px]:hidden" aria-label="Main"><a class="rounded-lg bg-[#f1f5f9] px-[11px] py-[7px] text-[.9rem] font-[550] text-[#0f172a] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#" aria-current="page">Overview</a><a class="rounded-lg px-[11px] py-[7px] text-[.9rem] font-[550] text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#">Invoices</a><a class="rounded-lg px-[11px] py-[7px] text-[.9rem] font-[550] text-[#475569] no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#">Reports</a></nav>
    <div class="relative max-[560px]:static" data-nf>
      <button class="relative grid size-[42px] cursor-pointer place-items-center rounded-xl border border-transparent bg-transparent p-0 text-[#334155] hover:bg-[#eef2ff] hover:text-[#4f46e5] aria-expanded:bg-[#eef2ff] aria-expanded:text-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" aria-expanded="true" aria-controls="nf-panel" data-nf-bell>
        <svg class="size-[22px]" aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="absolute top-[3px] right-0.5 h-[19px] min-w-[19px] rounded-full border-2 border-white bg-[#dc2626] px-[5px] text-center text-[.6875rem] leading-[15px] font-bold text-white" data-nf-count>3<span class="sr-only"> unread</span></span>
      </button>
      <section class="absolute top-[calc(100%+10px)] -right-2 z-20 w-[400px] max-w-[calc(100vw-32px)] origin-top-right rounded-2xl border border-[#e2e8f0] bg-white shadow-[0_28px_56px_-24px_rgba(15,23,42,.35),0_4px_12px_rgba(15,23,42,.06)] animate-panel-in motion-reduce:animate-none max-[560px]:right-2 max-[560px]:left-2 max-[560px]:w-auto max-[560px]:max-w-none" id="nf-panel" aria-labelledby="nf-title" data-nf-panel>
        <div class="flex flex-wrap items-center gap-2.5 px-[18px] pt-4 pb-3"><h2 class="m-0 text-base font-bold text-[#0f172a]" id="nf-title">Notifications</h2><span class="rounded-full bg-[#eef2ff] px-2 py-0.5 text-xs font-[650] whitespace-nowrap text-[#4338ca]" 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-[#4338ca] hover:bg-[#eef2ff] disabled:cursor-default disabled:bg-transparent disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-nf-markall><svg class="size-4" aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <div class="max-h-[420px] overflow-y-auto border-t border-[#e2e8f0]">
          <h3 class="m-0 px-[18px] pt-3 pb-1.5 text-[.6875rem] font-bold tracking-[.08em] text-[#5b6474] uppercase">Today</h3>
          <ul class="m-0 list-none space-y-0.5 px-2 pt-0 pb-1.5">
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item data-unread><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#ddd6fe] text-[#4c1d95]" aria-hidden="true">AM<i class="absolute -right-[3px] -bottom-[3px] grid size-[18px] place-items-center rounded-full border-2 border-white bg-[#4f46e5] text-white"><svg class="size-2.5"><use href="#i-comment"/></svg></i></span>
              <span><b>Amara Mensah</b> commented on <b>Q3 budget review</b><q class="mt-1 block border-l-2 border-[#c7d2fe] pl-[9px] [quotes:none]">Can we move the travel line to Operations?</q><span class="mt-1 block text-xs text-[#5b6474]">12 minutes ago</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span><span class="sr-only" data-unread-label>(unread)</span></a></li>
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item data-unread><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#dcfce7] text-[#15803d]" aria-hidden="true"><svg class="size-[19px]"><use href="#i-cash"/></svg></span>
              <span>Payout of <b>$4,820.00</b> sent to your account ending 0921<span class="mt-1 block text-xs text-[#5b6474]">1 hour ago</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span><span class="sr-only" data-unread-label>(unread)</span></a></li>
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item data-unread><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#bfdbfe] text-[#1e3a8a]" aria-hidden="true">JT<i class="absolute -right-[3px] -bottom-[3px] grid size-[18px] place-items-center rounded-full border-2 border-white bg-[#4f46e5] text-white"><svg class="size-2.5"><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="mt-1 block text-xs text-[#5b6474]">3 hours ago</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span><span class="sr-only" data-unread-label>(unread)</span></a></li>
          </ul>
          <h3 class="m-0 px-[18px] pt-3 pb-1.5 text-[.6875rem] font-bold tracking-[.08em] text-[#5b6474] uppercase">Earlier</h3>
          <ul class="m-0 list-none space-y-0.5 px-2 pt-0 pb-1.5">
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#fef3c7] text-[#b45309]" aria-hidden="true"><svg class="size-[19px]"><use href="#i-clock"/></svg></span>
              <span>Your <b>Pro trial</b> ends in 3 days. Add a card to keep your reports.<span class="mt-1 block text-xs text-[#5b6474]">Yesterday</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span></a></li>
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#fecdd3] text-[#881337]" aria-hidden="true">SL<i class="absolute -right-[3px] -bottom-[3px] grid size-[18px] place-items-center rounded-full border-2 border-white bg-[#4f46e5] text-white"><svg class="size-2.5"><use href="#i-share"/></svg></i></span>
              <span><b>Sofia Lindqvist</b> shared <b>Vendor list 2026</b> with you<span class="mt-1 block text-xs text-[#5b6474]">Monday</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span></a></li>
          </ul>
        </div>
        <div class="flex items-center justify-between border-t border-[#e2e8f0] px-3 py-2.5"><a class="rounded-lg px-2 py-1.5 text-sm font-semibold text-[#4338ca] no-underline hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#">View all notifications</a><a class="grid size-[34px] place-items-center rounded-lg text-[#475569] hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#" aria-label="Notification settings"><svg class="size-[18px]" aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
    <span class="grid size-[34px] place-items-center rounded-full bg-[#fde68a] text-[.8rem] font-bold text-[#78350f]" aria-hidden="true">EV</span>
  </header>
  <div class="grid flex-1 gap-3 p-6" aria-hidden="true"><i class="h-[120px] rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i></div>
</div></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Dot badge on a side rail, compact panel</span>
<div class="relative h-[640px] rounded-[18px] border border-[#d6dce5] bg-[#f8fafc] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-28px_rgba(15,23,42,.3)] max-[600px]:h-[700px] flex h-[440px]! max-[600px]:h-[440px]!">
  <nav class="flex w-[68px] flex-col items-center gap-2.5 rounded-l-[18px] border-r border-[#e2e8f0] bg-white py-4" aria-label="Workspace">
    <span class="mb-2 grid size-[38px] place-items-center rounded-[11px] bg-[#4f46e5] font-extrabold text-white" aria-hidden="true">L</span>
    <div class="relative mt-auto" data-nf>
      <button class="relative grid size-[42px] cursor-pointer place-items-center rounded-xl border border-transparent bg-transparent p-0 text-[#334155] hover:bg-[#eef2ff] hover:text-[#4f46e5] aria-expanded:bg-[#eef2ff] aria-expanded:text-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" aria-expanded="false" aria-controls="nf-panel-2" data-nf-bell>
        <svg class="size-[22px]" aria-hidden="true"><use href="#i-bell"/></svg><span class="sr-only">Notifications</span>
        <span class="absolute top-2 right-[9px] size-2.5 rounded-full border-2 border-white bg-[#dc2626]" data-nf-count><span class="sr-only">, new</span></span>
      </button>
      <section class="absolute top-[calc(100%+10px)] -right-2 z-20 w-[400px] max-w-[calc(100vw-32px)] origin-top-right rounded-2xl border border-[#e2e8f0] bg-white shadow-[0_28px_56px_-24px_rgba(15,23,42,.35),0_4px_12px_rgba(15,23,42,.06)] animate-panel-in motion-reduce:animate-none top-auto! right-auto! -bottom-1.5 left-[calc(100%+14px)] w-[340px] origin-bottom-left max-[560px]:left-[calc(100%+8px)] max-[560px]:w-[min(340px,calc(100vw-120px))]" id="nf-panel-2" aria-labelledby="nf-title-2" hidden data-nf-panel>
        <div class="flex flex-wrap items-center gap-2.5 px-[18px] pt-4 pb-3"><h2 class="m-0 text-base font-bold text-[#0f172a]" id="nf-title-2">Notifications</h2>
          <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-[#4338ca] hover:bg-[#eef2ff] disabled:cursor-default disabled:bg-transparent disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-nf-markall><svg class="size-4" aria-hidden="true"><use href="#i-checks"/></svg>Mark all as read</button></div>
        <div class="max-h-[420px] overflow-y-auto border-t border-[#e2e8f0] max-h-[300px]!">
          <h3 class="m-0 px-[18px] pt-3 pb-1.5 text-[.6875rem] font-bold tracking-[.08em] text-[#5b6474] uppercase">Today</h3>
          <ul class="m-0 list-none space-y-0.5 px-2 pt-0 pb-1.5">
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item data-unread><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#bfdbfe] text-[#1e3a8a]" aria-hidden="true">JT<i class="absolute -right-[3px] -bottom-[3px] grid size-[18px] place-items-center rounded-full border-2 border-white bg-[#4f46e5] text-white"><svg class="size-2.5"><use href="#i-user"/></svg></i></span>
              <span><b>Jonas Tanaka</b> assigned you <b>Reconcile March invoices</b><span class="mt-1 block text-xs text-[#5b6474]">3 hours ago</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span><span class="sr-only" data-unread-label>(unread)</span></a></li>
          </ul>
          <h3 class="m-0 px-[18px] pt-3 pb-1.5 text-[.6875rem] font-bold tracking-[.08em] text-[#5b6474] uppercase">Earlier</h3>
          <ul class="m-0 list-none space-y-0.5 px-2 pt-0 pb-1.5">
            <li><a class="group relative flex gap-3 rounded-xl py-[11px] pr-[30px] pl-2.5 text-sm leading-[1.45] text-[#475569] no-underline hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5] [&_b]:font-[650] [&_b]:text-[#0f172a]" href="#" data-nf-item><span class="relative grid size-[38px] flex-none place-items-center rounded-full text-[.8rem] font-bold bg-[#dcfce7] text-[#15803d]" aria-hidden="true"><svg class="size-[19px]"><use href="#i-cash"/></svg></span>
              <span>March statement is ready to download<span class="mt-1 block text-xs text-[#5b6474]">Friday</span></span><span class="absolute top-1/2 right-3 -mt-[4.5px] hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block"></span></a></li>
          </ul>
        </div>
        <div class="flex items-center justify-between border-t border-[#e2e8f0] px-3 py-2.5"><a class="rounded-lg px-2 py-1.5 text-sm font-semibold text-[#4338ca] no-underline hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#">View all</a><a class="grid size-[34px] place-items-center rounded-lg text-[#475569] hover:bg-[#eef2ff] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#" aria-label="Notification settings"><svg class="size-[18px]" aria-hidden="true"><use href="#i-gear"/></svg></a></div>
        <p class="sr-only" role="status" data-nf-status></p>
      </section>
    </div>
  </nav>
  <div class="grid flex-1 gap-3 p-6" aria-hidden="true"><i class="h-[120px] rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i><i class="h-16 rounded-xl border border-[#edf0f5] bg-white"></i></div>
</div></div>
CSS
@theme {
  --animate-panel-in: panel-in .18s ease-out;
  @keyframes panel-in { from { opacity: 0; transform: translateY(-6px) scale(.98); } }
}
JavaScript
(function () {
  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('[data-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.removeAttribute('data-unread');
      var sr = item.querySelector('[data-unread-label]');
      if (sr) sr.remove();
    }

    bell.addEventListener('click', function () { setOpen(panel.hidden); });
    markAll && markAll.addEventListener('click', function () {
      panel.querySelectorAll('[data-unread]').forEach(read);
      refresh();
      if (status) status.textContent = 'All notifications marked as read';
    });
    panel.addEventListener('click', function (e) {
      var item = e.target.closest('[data-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();
  });
})();

About this set

Two notification bells with dropdown panels. The first sits in an app header with a red count badge and opens a 400px panel with a 3 new pill, a Mark all as read button, notifications grouped under Today and Earlier headings, and a footer with View all notifications and a settings link. Each item shows an avatar with a small badge for the kind of event (comment, assignment, share) or an icon for system events such as a payout, plus the message, an optional quoted comment and the time. Unread items are tinted and carry a dot. The second bell sits on a narrow side rail with a dot badge instead of a count and opens a compact panel to its right. The count is real text inside the button followed by visually hidden text, so the bell reads Notifications 3 unread, and it updates as items are read and disappears at zero. Clicking an item marks it read; Mark all as read clears every item, disables itself and announces the change. The Bootstrap version uses the Dropdown plugin with data-bs-auto-close=”outside”, so clicks inside the panel do not close it and the arrow keys move between items.

What’s included

  • Count badge read as part of the bell's name, updated as items are read
  • Today and Earlier groups with real headings
  • Mark all as read with a status announcement
  • Header bell with a count and side rail bell with a dot
  • Bootstrap .dropdown, .dropdown-item and .badge with arrow-key navigation

Accessibility

The bell has aria-expanded and aria-controls, and its name includes the unread count, for example Notifications 3 unread. The panel is a region named by its heading, groups are h3 headings over lists, unread items carry visually hidden (unread) text, and Mark all as read is announced through a polite status region. Esc closes the panel and returns focus to the bell, every control has a 2px focus outline, and the panel's open animation is off under prefers-reduced-motion.

Customise it

In plain CSS set --nf-brand, --nf-ink, --nf-text and --nf-line on .nf and .nf-topbar, the badge colour on .nf-count and the avatar tints on .nf-av.c1 to .c3. In Bootstrap the panel takes the --bs-dropdown-* variables; in Tailwind change the hex values in the bg-, text- and data-[unread]: utilities.