Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <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-chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18M8 15v2M12 11v6M16 7v10"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4.5" y="10.5" width="15" height="10" rx="2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5"/></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-archive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="5" rx="1.5"/><path d="M5 9v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9M10 13h4"/></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="M12 2v3M12 19v3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/></symbol>
  <symbol id="i-inbox" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1z"/></symbol>
</svg>

<div><span class="lbl">Tabs, filters, bulk select and an empty state</span>
<section class="nc" aria-labelledby="nc-title" data-nc>
  <div class="nc-head">
    <div><h2 id="nc-title">Notifications</h2><p class="nc-sub" data-nc-summary>8 notifications, 4 unread</p></div>
    <a class="nc-btn" href="#"><svg aria-hidden="true"><use href="#i-gear"/></svg>Settings</a>
  </div>
  <div class="nc-tabs" role="tablist" aria-label="Notification type">
    <button class="nc-tab" role="tab" type="button" id="nc-tab-all" aria-selected="true" aria-controls="nc-panel" data-nc-tab="all">All <span class="nc-num" data-nc-count="all">4</span><span class="sr-only"> unread</span></button>
    <button class="nc-tab" role="tab" type="button" id="nc-tab-mention" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="mention">Mentions <span class="nc-num" data-nc-count="mention">2</span><span class="sr-only"> unread</span></button>
    <button class="nc-tab" role="tab" type="button" id="nc-tab-system" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="system">System <span class="nc-num" data-nc-count="system">1</span><span class="sr-only"> unread</span></button>
  </div>
  <div class="nc-tools">
    <label class="nc-check"><input type="checkbox" data-nc-all>Select all</label>
    <span class="nc-selected" data-nc-selected>None selected</span>
    <button class="nc-btn" type="button" data-nc-read disabled><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark as read</button>
    <button class="nc-btn" type="button" data-nc-archive disabled><svg aria-hidden="true"><use href="#i-archive"/></svg>Archive</button>
    <div class="nc-filters">
      <label class="nc-switch"><input type="checkbox" role="switch" data-nc-unread><span class="nc-track" aria-hidden="true"></span>Unread only</label>
      <label class="sr-only" for="nc-project">Project</label>
      <select class="nc-select" id="nc-project" data-nc-project><option value="">All projects</option><option value="atlas">Atlas</option><option value="harbor">Harbor</option></select>
    </div>
  </div>
  <div id="nc-panel" role="tabpanel" aria-labelledby="nc-tab-all">
    <ul class="nc-list" data-nc-list>
      <li class="nc-item" data-type="mention" data-project="atlas" data-unread>
        <input type="checkbox" aria-label="Select: Noor Haddad mentioned you in Homepage hero">
        <span class="nc-av c1" aria-hidden="true">NH</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Noor Haddad</b> mentioned you in <b>Homepage hero</b></a>
          <p class="nc-quote">@Eli the new illustration is in Figma, can you check the crop on mobile?</p>
          <p class="nc-meta"><span class="nc-chip">Atlas</span><time datetime="2026-10-19T09:42">9:42 AM</time></p></div>
        <span class="nc-dot"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="nc-item" data-type="system" data-project="harbor" data-unread>
        <input type="checkbox" aria-label="Select: Deploy to production finished">
        <span class="nc-av ok" aria-hidden="true"><svg><use href="#i-rocket"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Deploy to production finished</b>: harbor-web v2.14.0 is live</a>
          <p class="nc-meta"><span class="nc-chip">Harbor</span><time datetime="2026-10-19T09:15">9:15 AM</time></p></div>
        <span class="nc-dot"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="nc-item" data-type="comment" data-project="atlas" data-unread>
        <input type="checkbox" aria-label="Select: Kai Brennan commented on Pricing table">
        <span class="nc-av c2" aria-hidden="true">KB</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Kai Brennan</b> commented on <b>Pricing table</b></a>
          <p class="nc-meta"><span class="nc-chip">Atlas</span><time datetime="2026-10-19T08:50">8:50 AM</time></p></div>
        <span class="nc-dot"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="nc-item" data-type="mention" data-project="harbor" data-unread>
        <input type="checkbox" aria-label="Select: Lucia Ferreira mentioned you in Q4 roadmap">
        <span class="nc-av c3" aria-hidden="true">LF</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Lucia Ferreira</b> mentioned you in <b>Q4 roadmap</b></a>
          <p class="nc-quote">@Eli can we ship the onboarding checklist before the November release?</p>
          <p class="nc-meta"><span class="nc-chip">Harbor</span><time datetime="2026-10-19T08:12">8:12 AM</time></p></div>
        <span class="nc-dot"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="nc-item" data-type="system" data-project="atlas">
        <input type="checkbox" aria-label="Select: Weekly usage report is ready">
        <span class="nc-av sys" aria-hidden="true"><svg><use href="#i-chart"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Weekly usage report</b> is ready: 1,284 sessions, up 12%</a>
          <p class="nc-meta"><span class="nc-chip">Atlas</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="nc-item" data-type="comment" data-project="harbor">
        <input type="checkbox" aria-label="Select: Theo Park assigned you Fix checkout spacing">
        <span class="nc-av c4" aria-hidden="true">TP</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Theo Park</b> assigned you <b>Fix checkout spacing</b></a>
          <p class="nc-meta"><span class="nc-chip">Harbor</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="nc-item" data-type="system" data-project="harbor">
        <input type="checkbox" aria-label="Select: Two-factor authentication turned on">
        <span class="nc-av sys" aria-hidden="true"><svg><use href="#i-lock"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Two-factor authentication</b> was turned on for your account</a>
          <p class="nc-meta"><span class="nc-chip">Harbor</span><time datetime="2026-10-14">Oct 14</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="nc-item" data-type="mention" data-project="atlas">
        <input type="checkbox" aria-label="Select: Noor Haddad mentioned you in Brand colours">
        <span class="nc-av c1" aria-hidden="true">NH</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Noor Haddad</b> mentioned you in <b>Brand colours</b></a>
          <p class="nc-meta"><span class="nc-chip">Atlas</span><time datetime="2026-10-13">Oct 13</time></p></div>
        <span class="nc-dot"></span>
      </li>
    </ul>
    <div class="nc-empty" hidden data-nc-empty>
      <div class="nc-empty-art" aria-hidden="true"><svg><use href="#i-inbox"/></svg></div>
      <h3>You’re all caught up</h3>
      <p>No notifications match this view. New ones show up here as they arrive.</p>
      <button class="nc-btn" type="button" data-nc-reset>Show all notifications</button>
    </div>
  </div>
  <p class="sr-only" role="status" data-nc-status></p>
</section></div>

<div><span class="lbl">Empty state on its own</span>
<section class="nc" aria-labelledby="nc-empty-title">
  <div class="nc-empty">
    <div class="nc-empty-art" aria-hidden="true"><svg><use href="#i-inbox"/></svg></div>
    <h3 id="nc-empty-title">No notifications yet</h3>
    <p>When teammates mention you, assign you work or a deploy finishes, you’ll see it here.</p>
    <a class="nc-btn" href="#"><svg aria-hidden="true"><use href="#i-gear"/></svg>Choose what you get</a>
  </div>
</section></div>
CSS
/* Notification centre: tabs filter one list (role="tablist" + a single tabpanel), the filters and bulk actions are
   ordinary form controls, and state lives in data attributes (data-unread, hidden) so the script stays small. */
.nc{--nc-brand:#4f46e5;--nc-ink:#0f172a;--nc-text:#475569;--nc-muted:#5b6474;--nc-line:#e2e8f0;
  background:#fff;border:1px solid var(--nc-line);border-radius:18px;box-shadow:0 1px 2px rgba(15,23,42,.05),0 24px 48px -30px rgba(15,23,42,.3);color:var(--nc-text)}
.nc [hidden]{display:none!important}
.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}
.nc-head{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px;padding:24px 24px 4px}
.nc-head h2{margin:0;font-size:1.375rem;letter-spacing:-.01em;color:var(--nc-ink)}
.nc-sub{margin:2px 0 0;font-size:.875rem;color:var(--nc-muted)}
.nc-btn{display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 13px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:var(--nc-ink);
  font:inherit;font-size:.8125rem;font-weight:600;text-decoration:none;cursor:pointer}
.nc-btn:hover{background:#f1f5f9}
.nc-btn:disabled{color:#64748b;background:#f8fafc;border-color:#e2e8f0;cursor:not-allowed}
.nc-btn svg{width:16px;height:16px}
/* Tabs */
.nc-tabs{display:flex;gap:4px;margin-top:14px;padding:0 16px;border-bottom:1px solid var(--nc-line);overflow-x:auto}
.nc-tab{position:relative;display:inline-flex;align-items:center;gap:8px;padding:12px 10px 13px;border:0;background:none;color:var(--nc-text);font:inherit;font-size:.9rem;font-weight:600;cursor:pointer;white-space:nowrap}
.nc-tab::after{content:"";position:absolute;left:8px;right:8px;bottom:-1px;height:2px;border-radius:2px;background:transparent}
.nc-tab:hover{color:var(--nc-ink)}
.nc-tab[aria-selected="true"]{color:var(--nc-brand)}
.nc-tab[aria-selected="true"]::after{background:var(--nc-brand)}
.nc-num{min-width:22px;padding:1px 7px;border-radius:999px;background:#f1f5f9;color:#334155;font-size:.75rem;font-weight:700;text-align:center}
.nc-tab[aria-selected="true"] .nc-num{background:#eef2ff;color:#4338ca}
/* Toolbar */
.nc-tools{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;padding:12px 24px;border-bottom:1px solid var(--nc-line);background:#fafbfc}
.nc-check{display:inline-flex;align-items:center;gap:9px;font-size:.875rem;font-weight:600;color:var(--nc-ink);cursor:pointer}
.nc input[type="checkbox"]:not([role]){width:18px;height:18px;margin:0;accent-color:var(--nc-brand);cursor:pointer}
.nc-selected{font-size:.8125rem;color:var(--nc-muted)}
.nc-filters{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-left:auto}
.nc-switch{position:relative;display:inline-flex;align-items:center;gap:9px;font-size:.875rem;font-weight:600;color:var(--nc-ink);cursor:pointer}
.nc-switch input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.nc-track{position:relative;width:36px;height:20px;border-radius:999px;background:#7c8aa0;transition:background .2s}
.nc-track::before{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(15,23,42,.3);transition:transform .2s}
.nc-switch input:checked+.nc-track{background:var(--nc-brand)}
.nc-switch input:checked+.nc-track::before{transform:translateX(16px)}
.nc-select{height:36px;padding:0 10px;border:1px solid #cbd5e1;border-radius:10px;background:#fff;color:var(--nc-ink);font:inherit;font-size:.875rem}
/* List */
.nc-list{margin:0;padding:6px 12px 12px;list-style:none}
.nc-item{position:relative;display:flex;align-items:flex-start;gap:14px;padding:14px 40px 14px 12px;border-radius:12px}
.nc-item+.nc-item{margin-top:2px}
.nc-item:hover{background:#f8fafc}
.nc-item[data-unread]{background:#f5f7ff}
.nc-item[data-unread]::before{content:"";position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:3px;background:var(--nc-brand)}
.nc-item>input{margin-top:10px!important}
.nc-av{flex:none;display:grid;place-items:center;width:40px;height:40px;border-radius:50%;font-size:.8125rem;font-weight:700}
.nc-av svg{width:20px;height:20px}
.nc-av.c1{background:#ddd6fe;color:#4c1d95}.nc-av.c2{background:#bfdbfe;color:#1e3a8a}.nc-av.c3{background:#fecdd3;color:#881337}.nc-av.c4{background:#fde68a;color:#78350f}
.nc-av.sys{background:#e2e8f0;color:#334155}.nc-av.ok{background:#dcfce7;color:#15803d}
.nc-main{flex:1;min-width:0;font-size:.9rem;line-height:1.45}
.nc-link{color:var(--nc-text);text-decoration:none}
.nc-link b{color:var(--nc-ink);font-weight:650}
.nc-link:hover{text-decoration:underline}
.nc-quote{margin:5px 0 0;padding:8px 12px;border-radius:10px;background:#fff;border:1px solid var(--nc-line);color:#334155}
.nc-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:6px 0 0;font-size:.75rem;color:var(--nc-muted)}
.nc-chip{padding:1px 8px;border-radius:999px;background:#f1f5f9;color:#334155;font-weight:600}
.nc-dot{position:absolute;top:22px;right:16px;width:9px;height:9px;border-radius:50%;background:var(--nc-brand)}
.nc-item:not([data-unread]) .nc-dot{display:none}
/* Empty state */
.nc-empty{padding:48px 24px 56px;text-align:center}
.nc-empty-art{display:grid;place-items:center;width:72px;height:72px;margin:0 auto 16px;border-radius:22px;background:linear-gradient(135deg,#eef2ff,#e0e7ff);color:var(--nc-brand)}
.nc-empty-art svg{width:34px;height:34px}
.nc-empty h3{margin:0;font-size:1.0625rem;color:var(--nc-ink)}
.nc-empty p{max-width:340px;margin:6px auto 18px;font-size:.9rem}
.nc-btn:focus-visible,.nc-tab:focus-visible,.nc-select:focus-visible,.nc-link:focus-visible,.nc input[type="checkbox"]:not([role]):focus-visible{outline:2px solid var(--nc-brand);outline-offset:2px}
.nc-switch input:focus-visible+.nc-track{outline:2px solid var(--nc-brand);outline-offset:2px}
@media (max-width:600px){
  .nc-head,.nc-tools{padding-left:16px;padding-right:16px}
  .nc-filters{margin-left:0}
  .nc-list{padding:6px 4px 8px}
  .nc-item{gap:10px;padding-right:30px}
  .nc-av{width:34px;height:34px}
  .nc-dot{right:12px}
}
@media (prefers-reduced-motion:reduce){.nc-track,.nc-track::before{transition:none}}
JavaScript
(function () {
  document.querySelectorAll('[data-nc]').forEach(function (nc) {
    var tabs = [].slice.call(nc.querySelectorAll('[data-nc-tab]'));
    var panel = nc.querySelector('[role="tabpanel"]');
    var items = function () { return [].slice.call(nc.querySelectorAll('[data-nc-list] > li')); };
    var all = nc.querySelector('[data-nc-all]');
    var unreadOnly = nc.querySelector('[data-nc-unread]');
    var project = nc.querySelector('[data-nc-project]');
    var empty = nc.querySelector('[data-nc-empty]');
    var status = nc.querySelector('[data-nc-status]');
    var selectedLabel = nc.querySelector('[data-nc-selected]');
    var readBtn = nc.querySelector('[data-nc-read]');
    var archiveBtn = nc.querySelector('[data-nc-archive]');
    var type = 'all';

    function box(li) { return li.querySelector('input[type="checkbox"]'); }
    function visible() { return items().filter(function (li) { return !li.hidden; }); }
    function selected() { return visible().filter(function (li) { return box(li).checked; }); }

    function apply(announce) {
      items().forEach(function (li) {
        var show = (type === 'all' || li.dataset.type === type) &&
          (!unreadOnly.checked || li.hasAttribute('data-unread')) &&
          (!project.value || li.dataset.project === project.value);
        li.hidden = !show;
        if (!show) box(li).checked = false;
      });
      var shown = visible().length;
      empty.hidden = shown > 0;
      ['all', 'mention', 'system'].forEach(function (t) {
        var el = nc.querySelector('[data-nc-count="' + t + '"]');
        if (el) el.textContent = items().filter(function (li) { return li.hasAttribute('data-unread') && (t === 'all' || li.dataset.type === t); }).length;
      });
      var unread = items().filter(function (li) { return li.hasAttribute('data-unread'); }).length;
      nc.querySelector('[data-nc-summary]').textContent = items().length + ' notifications, ' + unread + ' unread';
      sync();
      if (announce) status.textContent = shown ? 'Showing ' + shown + ' notification' + (shown === 1 ? '' : 's') : 'No notifications match these filters';
    }

    function sync() {
      var vis = visible().length, sel = selected().length;
      all.checked = vis > 0 && sel === vis;
      all.indeterminate = sel > 0 && sel < vis;
      all.disabled = vis === 0;
      selectedLabel.textContent = sel ? sel + ' selected' : 'None selected';
      readBtn.disabled = archiveBtn.disabled = sel === 0;
    }

    function markRead(li) {
      li.removeAttribute('data-unread');
      var sr = li.querySelector('[data-nc-unread-label]');
      if (sr) sr.remove();
    }

    function selectTab(tab, focus) {
      tabs.forEach(function (t) {
        var on = t === tab;
        t.setAttribute('aria-selected', on);
        t.tabIndex = on ? 0 : -1;
      });
      panel.setAttribute('aria-labelledby', tab.id);
      type = tab.dataset.ncTab;
      if (focus) tab.focus();
      apply(true);
    }

    tabs.forEach(function (tab, i) {
      tab.addEventListener('click', function () { selectTab(tab); });
      tab.addEventListener('keydown', function (e) {
        var j = e.key === 'ArrowRight' ? i + 1 : e.key === 'ArrowLeft' ? i - 1 : e.key === 'Home' ? 0 : e.key === 'End' ? tabs.length - 1 : null;
        if (j === null) return;
        e.preventDefault();
        selectTab(tabs[(j + tabs.length) % tabs.length], true);
      });
    });
    unreadOnly.addEventListener('change', function () { apply(true); });
    project.addEventListener('change', function () { apply(true); });
    all.addEventListener('change', function () { visible().forEach(function (li) { box(li).checked = all.checked; }); sync(); });
    nc.querySelector('[data-nc-list]').addEventListener('change', sync);
    nc.querySelector('[data-nc-list]').addEventListener('click', function (e) {
      var link = e.target.closest('a');
      if (!link) return;
      e.preventDefault(); // demo links; remove when they point somewhere
      markRead(link.closest('li')); apply(false);
    });
    readBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { markRead(li); box(li).checked = false; });
      apply(false);
      status.textContent = sel.length + ' marked as read';
    });
    archiveBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { li.remove(); });
      apply(false);
      status.textContent = sel.length + ' archived';
      (all.disabled ? nc.querySelector('[data-nc-reset]') : all).focus();
    });
    nc.querySelector('[data-nc-reset]').addEventListener('click', function () {
      unreadOnly.checked = false; project.value = '';
      selectTab(tabs[0], true);
    });
    apply(false);
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-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-chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18M8 15v2M12 11v6M16 7v10"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4.5" y="10.5" width="15" height="10" rx="2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5"/></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-archive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="5" rx="1.5"/><path d="M5 9v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9M10 13h4"/></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="M12 2v3M12 19v3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/></symbol>
  <symbol id="i-inbox" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1z"/></symbol>
</svg>

<div><span class="lbl">Tabs, filters, bulk select and an empty state</span>
<section class="card nc" aria-labelledby="nc-title" data-nc>
  <div class="nc-head d-flex flex-wrap align-items-start justify-content-between gap-3">
    <div><h2 id="nc-title">Notifications</h2><p class="nc-sub" data-nc-summary>8 notifications, 4 unread</p></div>
    <a class="btn btn-outline-secondary" href="#"><svg aria-hidden="true"><use href="#i-gear"/></svg>Settings</a>
  </div>
  <ul class="nav nav-underline nc-tabs" role="tablist" aria-label="Notification type">
    <li class="nav-item" role="presentation"><button class="nav-link" role="tab" type="button" id="nc-tab-all" aria-selected="true" aria-controls="nc-panel" data-nc-tab="all">All <span class="badge rounded-pill nc-num" data-nc-count="all">4</span><span class="visually-hidden"> unread</span></button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" role="tab" type="button" id="nc-tab-mention" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="mention">Mentions <span class="badge rounded-pill nc-num" data-nc-count="mention">2</span><span class="visually-hidden"> unread</span></button></li>
    <li class="nav-item" role="presentation"><button class="nav-link" role="tab" type="button" id="nc-tab-system" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="system">System <span class="badge rounded-pill nc-num" data-nc-count="system">1</span><span class="visually-hidden"> unread</span></button></li>
  </ul>
  <div class="nc-tools d-flex flex-wrap align-items-center column-gap-3 row-gap-2">
    <div class="form-check d-inline-flex align-items-center gap-2 m-0 p-0"><input class="form-check-input m-0" type="checkbox" id="nc-all" data-nc-all><label class="form-check-label" for="nc-all">Select all</label></div>
    <span class="nc-selected" data-nc-selected>None selected</span>
    <button class="btn btn-outline-secondary" type="button" data-nc-read disabled><svg aria-hidden="true"><use href="#i-checks"/></svg>Mark as read</button>
    <button class="btn btn-outline-secondary" type="button" data-nc-archive disabled><svg aria-hidden="true"><use href="#i-archive"/></svg>Archive</button>
    <div class="nc-filters d-flex flex-wrap align-items-center gap-3 ms-auto">
      <div class="form-check form-switch d-inline-flex align-items-center gap-2 m-0 p-0"><input class="form-check-input m-0" type="checkbox" role="switch" id="nc-unread" data-nc-unread><label class="form-check-label" for="nc-unread">Unread only</label></div>
      <label class="visually-hidden" for="nc-project">Project</label>
      <select class="form-select" id="nc-project" data-nc-project><option value="">All projects</option><option value="atlas">Atlas</option><option value="harbor">Harbor</option></select>
    </div>
  </div>
  <div id="nc-panel" role="tabpanel" aria-labelledby="nc-tab-all">
    <ul class="list-group nc-list" data-nc-list>
      <li class="list-group-item nc-item" data-type="mention" data-project="atlas" data-unread>
        <input class="form-check-input" type="checkbox" aria-label="Select: Noor Haddad mentioned you in Homepage hero">
        <span class="nc-av c1" aria-hidden="true">NH</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Noor Haddad</b> mentioned you in <b>Homepage hero</b></a>
          <p class="nc-quote">@Eli the new illustration is in Figma, can you check the crop on mobile?</p>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Atlas</span><time datetime="2026-10-19T09:42">9:42 AM</time></p></div>
        <span class="nc-dot"></span><span class="visually-hidden" data-nc-unread-label>Unread</span>
      </li>
      <li class="list-group-item nc-item" data-type="system" data-project="harbor" data-unread>
        <input class="form-check-input" type="checkbox" aria-label="Select: Deploy to production finished">
        <span class="nc-av ok" aria-hidden="true"><svg><use href="#i-rocket"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Deploy to production finished</b>: harbor-web v2.14.0 is live</a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Harbor</span><time datetime="2026-10-19T09:15">9:15 AM</time></p></div>
        <span class="nc-dot"></span><span class="visually-hidden" data-nc-unread-label>Unread</span>
      </li>
      <li class="list-group-item nc-item" data-type="comment" data-project="atlas" data-unread>
        <input class="form-check-input" type="checkbox" aria-label="Select: Kai Brennan commented on Pricing table">
        <span class="nc-av c2" aria-hidden="true">KB</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Kai Brennan</b> commented on <b>Pricing table</b></a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Atlas</span><time datetime="2026-10-19T08:50">8:50 AM</time></p></div>
        <span class="nc-dot"></span><span class="visually-hidden" data-nc-unread-label>Unread</span>
      </li>
      <li class="list-group-item nc-item" data-type="mention" data-project="harbor" data-unread>
        <input class="form-check-input" type="checkbox" aria-label="Select: Lucia Ferreira mentioned you in Q4 roadmap">
        <span class="nc-av c3" aria-hidden="true">LF</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Lucia Ferreira</b> mentioned you in <b>Q4 roadmap</b></a>
          <p class="nc-quote">@Eli can we ship the onboarding checklist before the November release?</p>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Harbor</span><time datetime="2026-10-19T08:12">8:12 AM</time></p></div>
        <span class="nc-dot"></span><span class="visually-hidden" data-nc-unread-label>Unread</span>
      </li>
      <li class="list-group-item nc-item" data-type="system" data-project="atlas">
        <input class="form-check-input" type="checkbox" aria-label="Select: Weekly usage report is ready">
        <span class="nc-av sys" aria-hidden="true"><svg><use href="#i-chart"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Weekly usage report</b> is ready: 1,284 sessions, up 12%</a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Atlas</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="list-group-item nc-item" data-type="comment" data-project="harbor">
        <input class="form-check-input" type="checkbox" aria-label="Select: Theo Park assigned you Fix checkout spacing">
        <span class="nc-av c4" aria-hidden="true">TP</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Theo Park</b> assigned you <b>Fix checkout spacing</b></a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Harbor</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="list-group-item nc-item" data-type="system" data-project="harbor">
        <input class="form-check-input" type="checkbox" aria-label="Select: Two-factor authentication turned on">
        <span class="nc-av sys" aria-hidden="true"><svg><use href="#i-lock"/></svg></span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Two-factor authentication</b> was turned on for your account</a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Harbor</span><time datetime="2026-10-14">Oct 14</time></p></div>
        <span class="nc-dot"></span>
      </li>
      <li class="list-group-item nc-item" data-type="mention" data-project="atlas">
        <input class="form-check-input" type="checkbox" aria-label="Select: Noor Haddad mentioned you in Brand colours">
        <span class="nc-av c1" aria-hidden="true">NH</span>
        <div class="nc-main"><a class="nc-link" href="#"><b>Noor Haddad</b> mentioned you in <b>Brand colours</b></a>
          <p class="nc-meta d-flex flex-wrap align-items-center gap-2"><span class="badge rounded-pill nc-chip">Atlas</span><time datetime="2026-10-13">Oct 13</time></p></div>
        <span class="nc-dot"></span>
      </li>
    </ul>
    <div class="nc-empty text-center" hidden data-nc-empty>
      <div class="nc-empty-art" aria-hidden="true"><svg><use href="#i-inbox"/></svg></div>
      <h3>You’re all caught up</h3>
      <p>No notifications match this view. New ones show up here as they arrive.</p>
      <button class="btn btn-outline-secondary" type="button" data-nc-reset>Show all notifications</button>
    </div>
  </div>
  <p class="visually-hidden" role="status" data-nc-status></p>
</section></div>

<div><span class="lbl">Empty state on its own</span>
<section class="card nc" aria-labelledby="nc-empty-title">
  <div class="nc-empty text-center">
    <div class="nc-empty-art" aria-hidden="true"><svg><use href="#i-inbox"/></svg></div>
    <h3 id="nc-empty-title">No notifications yet</h3>
    <p>When teammates mention you, assign you work or a deploy finishes, you’ll see it here.</p>
    <a class="btn btn-outline-secondary" href="#"><svg aria-hidden="true"><use href="#i-gear"/></svg>Choose what you get</a>
  </div>
</section></div>
CSS
/* Notification centre on Bootstrap: .nav-underline tabs (role="tablist" over one tabpanel), .form-check, .form-switch,
   .form-select, .list-group and .btn. State lives in data attributes (data-unread, hidden), so the script stays small. */
.nc{--nc-brand:#4f46e5;--nc-ink:#0f172a;--nc-text:#475569;--nc-muted:#5b6474;--nc-line:#e2e8f0;
  --bs-card-border-color:var(--nc-line);--bs-card-border-radius:18px;--bs-card-color:var(--nc-text);box-shadow:0 1px 2px rgba(15,23,42,.05),0 24px 48px -30px rgba(15,23,42,.3)}
.nc-head{padding:24px 24px 4px}
.nc-head h2{margin:0;font-size:1.375rem;font-weight:700;letter-spacing:-.01em;color:var(--nc-ink)}
.nc-sub{margin:2px 0 0;font-size:.875rem;color:var(--nc-muted)}
.nc .btn-outline-secondary{--bs-btn-color:var(--nc-ink);--bs-btn-border-color:#cbd5e1;--bs-btn-hover-color:var(--nc-ink);--bs-btn-hover-bg:#f1f5f9;--bs-btn-hover-border-color:#cbd5e1;
  --bs-btn-active-color:var(--nc-ink);--bs-btn-active-bg:#e2e8f0;--bs-btn-active-border-color:#cbd5e1;--bs-btn-disabled-color:#64748b;--bs-btn-disabled-bg:#f8fafc;--bs-btn-disabled-border-color:#e2e8f0;--bs-btn-disabled-opacity:1;
  --bs-btn-font-size:.8125rem;--bs-btn-font-weight:600;--bs-btn-border-radius:10px;--bs-btn-padding-x:13px;--bs-btn-focus-box-shadow:0 0 0 2px #fff,0 0 0 4px var(--nc-brand);
  display:inline-flex;align-items:center;gap:7px;height:36px;background:#fff}
.nc .btn-outline-secondary:disabled{background:var(--bs-btn-disabled-bg)}
.nc .btn svg{width:16px;height:16px}
.nc-tabs{--bs-nav-underline-gap:4px;--bs-nav-underline-border-width:2px;--bs-nav-link-color:var(--nc-text);--bs-nav-link-hover-color:var(--nc-ink);--bs-nav-link-font-weight:600;--bs-nav-link-font-size:.9rem;
  --bs-nav-underline-link-active-color:var(--nc-brand);margin-top:14px;padding:0 16px;border-bottom:1px solid var(--nc-line);flex-wrap:nowrap;overflow-x:auto}
.nc-tabs .nav-link{display:inline-flex;align-items:center;gap:8px;padding:12px 10px 13px;margin-bottom:-1px;white-space:nowrap}
.nc-tabs .nav-link[aria-selected="true"]{color:var(--nc-brand);border-bottom-color:currentcolor}
.nc-tabs .nav-link:focus-visible{outline:2px solid var(--nc-brand);outline-offset:-2px;box-shadow:none}
.nc-num{--bs-badge-font-size:.75rem;--bs-badge-padding-x:7px;--bs-badge-padding-y:3px;--bs-badge-color:#334155;min-width:22px;background:#f1f5f9}
.nc-tabs .nav-link[aria-selected="true"] .nc-num{background:#eef2ff;color:#4338ca}
.nc-tools{padding:12px 24px;border-bottom:1px solid var(--nc-line);background:#fafbfc}
.nc .form-check-input{--bs-form-check-bg:#fff;width:18px;height:18px;margin-top:0;border-color:#7c8aa0;cursor:pointer}
.nc .form-check-input:checked,.nc .form-check-input:indeterminate{background-color:var(--nc-brand);border-color:var(--nc-brand)}
.nc .form-check-input:focus{border-color:var(--nc-brand);box-shadow:none}
.nc .form-check-input:focus-visible{outline:2px solid var(--nc-brand);outline-offset:2px}
.nc .form-check-label{font-size:.875rem;font-weight:600;color:var(--nc-ink);cursor:pointer}
.nc .form-switch .form-check-input{width:36px;height:20px;background-color:#7c8aa0;border-color:#7c8aa0;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}
.nc .form-switch .form-check-input:checked{background-color:var(--nc-brand);border-color:var(--nc-brand)}
.nc-selected{font-size:.8125rem;color:var(--nc-muted)}
.nc .form-select{height:36px;width:auto;padding-top:0;padding-bottom:0;border-color:#cbd5e1;border-radius:10px;font-size:.875rem;color:var(--nc-ink)}
.nc .form-select:focus{border-color:var(--nc-brand);box-shadow:0 0 0 2px #fff,0 0 0 4px var(--nc-brand)}
.nc-list{--bs-list-group-border-width:0;--bs-list-group-bg:transparent;--bs-list-group-color:var(--nc-text);padding:6px 12px 12px}
.nc-item{position:relative;display:flex;align-items:flex-start;gap:14px;padding:14px 40px 14px 12px;border-radius:12px!important}
.nc-item+.nc-item{margin-top:2px}
.nc-item:hover{background:#f8fafc}
.nc-item[data-unread]{background:#f5f7ff}
.nc-item[data-unread]::before{content:"";position:absolute;left:0;top:14px;bottom:14px;width:3px;border-radius:3px;background:var(--nc-brand)}
.nc-item>.form-check-input{flex:none;margin-top:10px}
.nc-av{flex:none;display:grid;place-items:center;width:40px;height:40px;border-radius:50%;font-size:.8125rem;font-weight:700}
.nc-av svg{width:20px;height:20px}
.nc-av.c1{background:#ddd6fe;color:#4c1d95}.nc-av.c2{background:#bfdbfe;color:#1e3a8a}.nc-av.c3{background:#fecdd3;color:#881337}.nc-av.c4{background:#fde68a;color:#78350f}
.nc-av.sys{background:#e2e8f0;color:#334155}.nc-av.ok{background:#dcfce7;color:#15803d}
.nc-main{flex:1;min-width:0;font-size:.9rem;line-height:1.45}
.nc-link{color:var(--nc-text);text-decoration:none}
.nc-link b{color:var(--nc-ink);font-weight:650}
.nc-link:hover{color:var(--nc-text);text-decoration:underline}
.nc-link:focus-visible{outline:2px solid var(--nc-brand);outline-offset:2px;border-radius:2px}
.nc-quote{margin:5px 0 0;padding:8px 12px;border-radius:10px;background:#fff;border:1px solid var(--nc-line);color:#334155}
.nc-meta{margin:6px 0 0;font-size:.75rem;color:var(--nc-muted)}
.nc-chip{--bs-badge-font-size:.75rem;--bs-badge-font-weight:600;--bs-badge-padding-x:8px;--bs-badge-padding-y:2px;--bs-badge-color:#334155;background:#f1f5f9}
.nc-dot{position:absolute;top:22px;right:16px;width:9px;height:9px;border-radius:50%;background:var(--nc-brand)}
.nc-item:not([data-unread]) .nc-dot{display:none}
.nc-empty{padding:48px 24px 56px}
.nc-empty-art{display:grid;place-items:center;width:72px;height:72px;margin:0 auto 16px;border-radius:22px;background:linear-gradient(135deg,#eef2ff,#e0e7ff);color:var(--nc-brand)}
.nc-empty-art svg{width:34px;height:34px}
.nc-empty h3{margin:0;font-size:1.0625rem;font-weight:700;color:var(--nc-ink)}
.nc-empty p{max-width:340px;margin:6px auto 18px;font-size:.9rem}
@media (max-width:600px){
  .nc-head,.nc-tools{padding-left:16px;padding-right:16px}
  .nc-filters{margin-left:0!important}
  .nc-list{padding:6px 4px 8px}
  .nc-item{gap:10px;padding-right:30px}
  .nc-av{width:34px;height:34px}
  .nc-dot{right:12px}
}
@media (prefers-reduced-motion:reduce){.nc .form-switch .form-check-input{transition:none}}
JavaScript
(function () {
  document.querySelectorAll('[data-nc]').forEach(function (nc) {
    var tabs = [].slice.call(nc.querySelectorAll('[data-nc-tab]'));
    var panel = nc.querySelector('[role="tabpanel"]');
    var items = function () { return [].slice.call(nc.querySelectorAll('[data-nc-list] > li')); };
    var all = nc.querySelector('[data-nc-all]');
    var unreadOnly = nc.querySelector('[data-nc-unread]');
    var project = nc.querySelector('[data-nc-project]');
    var empty = nc.querySelector('[data-nc-empty]');
    var status = nc.querySelector('[data-nc-status]');
    var selectedLabel = nc.querySelector('[data-nc-selected]');
    var readBtn = nc.querySelector('[data-nc-read]');
    var archiveBtn = nc.querySelector('[data-nc-archive]');
    var type = 'all';

    function box(li) { return li.querySelector('input[type="checkbox"]'); }
    function visible() { return items().filter(function (li) { return !li.hidden; }); }
    function selected() { return visible().filter(function (li) { return box(li).checked; }); }

    function apply(announce) {
      items().forEach(function (li) {
        var show = (type === 'all' || li.dataset.type === type) &&
          (!unreadOnly.checked || li.hasAttribute('data-unread')) &&
          (!project.value || li.dataset.project === project.value);
        li.hidden = !show;
        if (!show) box(li).checked = false;
      });
      var shown = visible().length;
      empty.hidden = shown > 0;
      ['all', 'mention', 'system'].forEach(function (t) {
        var el = nc.querySelector('[data-nc-count="' + t + '"]');
        if (el) el.textContent = items().filter(function (li) { return li.hasAttribute('data-unread') && (t === 'all' || li.dataset.type === t); }).length;
      });
      var unread = items().filter(function (li) { return li.hasAttribute('data-unread'); }).length;
      nc.querySelector('[data-nc-summary]').textContent = items().length + ' notifications, ' + unread + ' unread';
      sync();
      if (announce) status.textContent = shown ? 'Showing ' + shown + ' notification' + (shown === 1 ? '' : 's') : 'No notifications match these filters';
    }

    function sync() {
      var vis = visible().length, sel = selected().length;
      all.checked = vis > 0 && sel === vis;
      all.indeterminate = sel > 0 && sel < vis;
      all.disabled = vis === 0;
      selectedLabel.textContent = sel ? sel + ' selected' : 'None selected';
      readBtn.disabled = archiveBtn.disabled = sel === 0;
    }

    function markRead(li) {
      li.removeAttribute('data-unread');
      var sr = li.querySelector('[data-nc-unread-label]');
      if (sr) sr.remove();
    }

    function selectTab(tab, focus) {
      tabs.forEach(function (t) {
        var on = t === tab;
        t.setAttribute('aria-selected', on);
        t.tabIndex = on ? 0 : -1;
      });
      panel.setAttribute('aria-labelledby', tab.id);
      type = tab.dataset.ncTab;
      if (focus) tab.focus();
      apply(true);
    }

    tabs.forEach(function (tab, i) {
      tab.addEventListener('click', function () { selectTab(tab); });
      tab.addEventListener('keydown', function (e) {
        var j = e.key === 'ArrowRight' ? i + 1 : e.key === 'ArrowLeft' ? i - 1 : e.key === 'Home' ? 0 : e.key === 'End' ? tabs.length - 1 : null;
        if (j === null) return;
        e.preventDefault();
        selectTab(tabs[(j + tabs.length) % tabs.length], true);
      });
    });
    unreadOnly.addEventListener('change', function () { apply(true); });
    project.addEventListener('change', function () { apply(true); });
    all.addEventListener('change', function () { visible().forEach(function (li) { box(li).checked = all.checked; }); sync(); });
    nc.querySelector('[data-nc-list]').addEventListener('change', sync);
    nc.querySelector('[data-nc-list]').addEventListener('click', function (e) {
      var link = e.target.closest('a');
      if (!link) return;
      e.preventDefault(); // demo links; remove when they point somewhere
      markRead(link.closest('li')); apply(false);
    });
    readBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { markRead(li); box(li).checked = false; });
      apply(false);
      status.textContent = sel.length + ' marked as read';
    });
    archiveBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { li.remove(); });
      apply(false);
      status.textContent = sel.length + ' archived';
      (all.disabled ? nc.querySelector('[data-nc-reset]') : all).focus();
    });
    nc.querySelector('[data-nc-reset]').addEventListener('click', function () {
      unreadOnly.checked = false; project.value = '';
      selectTab(tabs[0], true);
    });
    apply(false);
  });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <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-chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18M8 15v2M12 11v6M16 7v10"/></symbol>
  <symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4.5" y="10.5" width="15" height="10" rx="2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5"/></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-archive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="5" rx="1.5"/><path d="M5 9v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9M10 13h4"/></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="M12 2v3M12 19v3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/></symbol>
  <symbol id="i-inbox" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5.1 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1z"/></symbol>
</svg>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Tabs, filters, bulk select and an empty state</span>
<section class="rounded-[18px] border border-[#e2e8f0] bg-white text-[#475569] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-30px_rgba(15,23,42,.3)]" aria-labelledby="nc-title" data-nc>
  <div class="flex flex-wrap items-start justify-between gap-3 px-6 pt-6 pb-1 max-[600px]:px-4">
    <div><h2 class="m-0 text-[1.375rem] font-bold tracking-[-.01em] text-[#0f172a]" id="nc-title">Notifications</h2><p class="mt-0.5 mb-0 text-sm text-[#5b6474]" data-nc-summary>8 notifications, 4 unread</p></div>
    <a class="inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold text-[#0f172a] no-underline hover:bg-[#f1f5f9] disabled:cursor-not-allowed disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><svg class="size-4" aria-hidden="true"><use href="#i-gear"/></svg>Settings</a>
  </div>
  <div class="mt-3.5 flex gap-1 overflow-x-auto border-b border-[#e2e8f0] px-4" role="tablist" aria-label="Notification type">
    <button class="group relative inline-flex cursor-pointer items-center gap-2 border-0 bg-transparent px-2.5 pt-3 pb-[13px] text-[.9rem] font-semibold whitespace-nowrap text-[#475569] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:rounded-sm hover:text-[#0f172a] aria-selected:text-[#4f46e5] aria-selected:after:bg-[#4f46e5] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5]" role="tab" type="button" id="nc-tab-all" aria-selected="true" aria-controls="nc-panel" data-nc-tab="all">All <span class="min-w-[22px] rounded-full bg-[#f1f5f9] px-[7px] py-px text-center text-xs font-bold text-[#334155] group-aria-selected:bg-[#eef2ff] group-aria-selected:text-[#4338ca]" data-nc-count="all">4</span><span class="sr-only"> unread</span></button>
    <button class="group relative inline-flex cursor-pointer items-center gap-2 border-0 bg-transparent px-2.5 pt-3 pb-[13px] text-[.9rem] font-semibold whitespace-nowrap text-[#475569] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:rounded-sm hover:text-[#0f172a] aria-selected:text-[#4f46e5] aria-selected:after:bg-[#4f46e5] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5]" role="tab" type="button" id="nc-tab-mention" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="mention">Mentions <span class="min-w-[22px] rounded-full bg-[#f1f5f9] px-[7px] py-px text-center text-xs font-bold text-[#334155] group-aria-selected:bg-[#eef2ff] group-aria-selected:text-[#4338ca]" data-nc-count="mention">2</span><span class="sr-only"> unread</span></button>
    <button class="group relative inline-flex cursor-pointer items-center gap-2 border-0 bg-transparent px-2.5 pt-3 pb-[13px] text-[.9rem] font-semibold whitespace-nowrap text-[#475569] after:absolute after:inset-x-2 after:-bottom-px after:h-0.5 after:rounded-sm hover:text-[#0f172a] aria-selected:text-[#4f46e5] aria-selected:after:bg-[#4f46e5] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#4f46e5]" role="tab" type="button" id="nc-tab-system" aria-selected="false" aria-controls="nc-panel" tabindex="-1" data-nc-tab="system">System <span class="min-w-[22px] rounded-full bg-[#f1f5f9] px-[7px] py-px text-center text-xs font-bold text-[#334155] group-aria-selected:bg-[#eef2ff] group-aria-selected:text-[#4338ca]" data-nc-count="system">1</span><span class="sr-only"> unread</span></button>
  </div>
  <div class="flex flex-wrap items-center gap-x-3.5 gap-y-2.5 border-b border-[#e2e8f0] bg-[#fafbfc] px-6 py-3 max-[600px]:px-4">
    <label class="inline-flex cursor-pointer items-center gap-[9px] text-sm font-semibold text-[#0f172a]"><input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="checkbox" data-nc-all>Select all</label>
    <span class="text-[.8125rem] text-[#5b6474]" data-nc-selected>None selected</span>
    <button class="inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold text-[#0f172a] no-underline hover:bg-[#f1f5f9] disabled:cursor-not-allowed disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-nc-read disabled><svg class="size-4" aria-hidden="true"><use href="#i-checks"/></svg>Mark as read</button>
    <button class="inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold text-[#0f172a] no-underline hover:bg-[#f1f5f9] disabled:cursor-not-allowed disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-nc-archive disabled><svg class="size-4" aria-hidden="true"><use href="#i-archive"/></svg>Archive</button>
    <div class="ml-auto flex flex-wrap items-center gap-3 max-[600px]:ml-0">
      <label class="inline-flex cursor-pointer items-center gap-[9px] text-sm font-semibold text-[#0f172a]"><input class="peer sr-only" type="checkbox" role="switch" data-nc-unread><span class="relative h-5 w-9 rounded-full bg-[#7c8aa0] transition-colors duration-200 peer-checked:bg-[#4f46e5] peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#4f46e5] motion-reduce:transition-none before:absolute before:top-0.5 before:left-0.5 before:size-4 before:rounded-full before:bg-white before:shadow-[0_1px_2px_rgba(15,23,42,.3)] before:transition-transform before:duration-200 peer-checked:before:translate-x-4 motion-reduce:before:transition-none" aria-hidden="true"></span>Unread only</label>
      <label class="sr-only" for="nc-project">Project</label>
      <select class="h-9 cursor-pointer rounded-[10px] border border-[#cbd5e1] bg-white px-2.5 text-sm text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" id="nc-project" data-nc-project><option value="">All projects</option><option value="atlas">Atlas</option><option value="harbor">Harbor</option></select>
    </div>
  </div>
  <div id="nc-panel" role="tabpanel" aria-labelledby="nc-tab-all">
    <ul class="m-0 list-none space-y-0.5 px-3 pt-1.5 pb-3 max-[600px]:px-1 max-[600px]:pb-2" data-nc-list>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="mention" data-project="atlas" data-unread>
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Noor Haddad mentioned you in Homepage hero">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#ddd6fe] text-[#4c1d95]" aria-hidden="true">NH</span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Noor Haddad</b> mentioned you in <b>Homepage hero</b></a>
          <p class="mt-[5px] mb-0 rounded-[10px] border border-[#e2e8f0] bg-white px-3 py-2 text-[#334155]">@Eli the new illustration is in Figma, can you check the crop on mobile?</p>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Atlas</span><time datetime="2026-10-19T09:42">9:42 AM</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="system" data-project="harbor" data-unread>
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Deploy to production finished">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#dcfce7] text-[#15803d]" aria-hidden="true"><svg class="size-5"><use href="#i-rocket"/></svg></span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Deploy to production finished</b>: harbor-web v2.14.0 is live</a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Harbor</span><time datetime="2026-10-19T09:15">9:15 AM</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="comment" data-project="atlas" data-unread>
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Kai Brennan commented on Pricing table">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#bfdbfe] text-[#1e3a8a]" aria-hidden="true">KB</span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Kai Brennan</b> commented on <b>Pricing table</b></a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Atlas</span><time datetime="2026-10-19T08:50">8:50 AM</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="mention" data-project="harbor" data-unread>
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Lucia Ferreira mentioned you in Q4 roadmap">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#fecdd3] text-[#881337]" aria-hidden="true">LF</span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Lucia Ferreira</b> mentioned you in <b>Q4 roadmap</b></a>
          <p class="mt-[5px] mb-0 rounded-[10px] border border-[#e2e8f0] bg-white px-3 py-2 text-[#334155]">@Eli can we ship the onboarding checklist before the November release?</p>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Harbor</span><time datetime="2026-10-19T08:12">8:12 AM</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span><span class="sr-only" data-nc-unread-label>Unread</span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="system" data-project="atlas">
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Weekly usage report is ready">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#e2e8f0] text-[#334155]" aria-hidden="true"><svg class="size-5"><use href="#i-chart"/></svg></span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Weekly usage report</b> is ready: 1,284 sessions, up 12%</a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Atlas</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="comment" data-project="harbor">
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Theo Park assigned you Fix checkout spacing">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#fde68a] text-[#78350f]" aria-hidden="true">TP</span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Theo Park</b> assigned you <b>Fix checkout spacing</b></a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Harbor</span><time datetime="2026-10-18">Yesterday</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="system" data-project="harbor">
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Two-factor authentication turned on">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#e2e8f0] text-[#334155]" aria-hidden="true"><svg class="size-5"><use href="#i-lock"/></svg></span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Two-factor authentication</b> was turned on for your account</a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Harbor</span><time datetime="2026-10-14">Oct 14</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span>
      </li>
      <li class="group relative flex items-start gap-3.5 rounded-xl py-3.5 pr-10 pl-3 hover:bg-[#f8fafc] data-[unread]:bg-[#f5f7ff] data-[unread]:before:absolute data-[unread]:before:top-3.5 data-[unread]:before:bottom-3.5 data-[unread]:before:left-0 data-[unread]:before:w-[3px] data-[unread]:before:rounded-[3px] data-[unread]:before:bg-[#4f46e5] max-[600px]:gap-2.5 max-[600px]:pr-[30px]" data-type="mention" data-project="atlas">
        <input class="m-0 size-[18px] cursor-pointer accent-[#4f46e5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5] mt-2.5!" type="checkbox" aria-label="Select: Noor Haddad mentioned you in Brand colours">
        <span class="grid size-10 flex-none place-items-center rounded-full text-[.8125rem] font-bold max-[600px]:size-[34px] bg-[#ddd6fe] text-[#4c1d95]" aria-hidden="true">NH</span>
        <div class="min-w-0 flex-1 text-[.9rem] leading-[1.45]"><a class="text-[#475569] no-underline hover:underline [&_b]:font-[650] [&_b]:text-[#0f172a] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><b>Noor Haddad</b> mentioned you in <b>Brand colours</b></a>
          <p class="mt-1.5 mb-0 flex flex-wrap items-center gap-2 text-xs text-[#5b6474]"><span class="rounded-full bg-[#f1f5f9] px-2 py-px font-semibold text-[#334155]">Atlas</span><time datetime="2026-10-13">Oct 13</time></p></div>
        <span class="absolute top-[22px] right-4 hidden size-[9px] rounded-full bg-[#4f46e5] group-data-[unread]:block max-[600px]:right-3"></span>
      </li>
    </ul>
    <div class="px-6 pt-12 pb-14 text-center" hidden data-nc-empty>
      <div class="mx-auto mb-4 grid size-[72px] place-items-center rounded-[22px] bg-linear-135 from-[#eef2ff] to-[#e0e7ff] text-[#4f46e5]" aria-hidden="true"><svg class="size-[34px]"><use href="#i-inbox"/></svg></div>
      <h3 class="m-0 text-[1.0625rem] font-bold text-[#0f172a]">You’re all caught up</h3>
      <p class="mx-auto mt-1.5 mb-[18px] max-w-[340px] text-[.9rem]">No notifications match this view. New ones show up here as they arrive.</p>
      <button class="inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold text-[#0f172a] no-underline hover:bg-[#f1f5f9] disabled:cursor-not-allowed disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" type="button" data-nc-reset>Show all notifications</button>
    </div>
  </div>
  <p class="sr-only" role="status" data-nc-status></p>
</section></div>

<div><span class="mb-3.5 block text-[.72rem] tracking-[.12em] text-[#5b6474] uppercase">Empty state on its own</span>
<section class="rounded-[18px] border border-[#e2e8f0] bg-white text-[#475569] shadow-[0_1px_2px_rgba(15,23,42,.05),0_24px_48px_-30px_rgba(15,23,42,.3)]" aria-labelledby="nc-empty-title">
  <div class="px-6 pt-12 pb-14 text-center">
    <div class="mx-auto mb-4 grid size-[72px] place-items-center rounded-[22px] bg-linear-135 from-[#eef2ff] to-[#e0e7ff] text-[#4f46e5]" aria-hidden="true"><svg class="size-[34px]"><use href="#i-inbox"/></svg></div>
    <h3 class="m-0 text-[1.0625rem] font-bold text-[#0f172a]" id="nc-empty-title">No notifications yet</h3>
    <p class="mx-auto mt-1.5 mb-[18px] max-w-[340px] text-[.9rem]">When teammates mention you, assign you work or a deploy finishes, you’ll see it here.</p>
    <a class="inline-flex h-9 cursor-pointer items-center gap-[7px] rounded-[10px] border border-[#cbd5e1] bg-white px-[13px] text-[.8125rem] font-semibold text-[#0f172a] no-underline hover:bg-[#f1f5f9] disabled:cursor-not-allowed disabled:border-[#e2e8f0] disabled:bg-[#f8fafc] disabled:text-[#64748b] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" href="#"><svg class="size-4" aria-hidden="true"><use href="#i-gear"/></svg>Choose what you get</a>
  </div>
</section></div>
CSS
/* Utilities only: no custom CSS needed for this set. */
JavaScript
(function () {
  document.querySelectorAll('[data-nc]').forEach(function (nc) {
    var tabs = [].slice.call(nc.querySelectorAll('[data-nc-tab]'));
    var panel = nc.querySelector('[role="tabpanel"]');
    var items = function () { return [].slice.call(nc.querySelectorAll('[data-nc-list] > li')); };
    var all = nc.querySelector('[data-nc-all]');
    var unreadOnly = nc.querySelector('[data-nc-unread]');
    var project = nc.querySelector('[data-nc-project]');
    var empty = nc.querySelector('[data-nc-empty]');
    var status = nc.querySelector('[data-nc-status]');
    var selectedLabel = nc.querySelector('[data-nc-selected]');
    var readBtn = nc.querySelector('[data-nc-read]');
    var archiveBtn = nc.querySelector('[data-nc-archive]');
    var type = 'all';

    function box(li) { return li.querySelector('input[type="checkbox"]'); }
    function visible() { return items().filter(function (li) { return !li.hidden; }); }
    function selected() { return visible().filter(function (li) { return box(li).checked; }); }

    function apply(announce) {
      items().forEach(function (li) {
        var show = (type === 'all' || li.dataset.type === type) &&
          (!unreadOnly.checked || li.hasAttribute('data-unread')) &&
          (!project.value || li.dataset.project === project.value);
        li.hidden = !show;
        if (!show) box(li).checked = false;
      });
      var shown = visible().length;
      empty.hidden = shown > 0;
      ['all', 'mention', 'system'].forEach(function (t) {
        var el = nc.querySelector('[data-nc-count="' + t + '"]');
        if (el) el.textContent = items().filter(function (li) { return li.hasAttribute('data-unread') && (t === 'all' || li.dataset.type === t); }).length;
      });
      var unread = items().filter(function (li) { return li.hasAttribute('data-unread'); }).length;
      nc.querySelector('[data-nc-summary]').textContent = items().length + ' notifications, ' + unread + ' unread';
      sync();
      if (announce) status.textContent = shown ? 'Showing ' + shown + ' notification' + (shown === 1 ? '' : 's') : 'No notifications match these filters';
    }

    function sync() {
      var vis = visible().length, sel = selected().length;
      all.checked = vis > 0 && sel === vis;
      all.indeterminate = sel > 0 && sel < vis;
      all.disabled = vis === 0;
      selectedLabel.textContent = sel ? sel + ' selected' : 'None selected';
      readBtn.disabled = archiveBtn.disabled = sel === 0;
    }

    function markRead(li) {
      li.removeAttribute('data-unread');
      var sr = li.querySelector('[data-nc-unread-label]');
      if (sr) sr.remove();
    }

    function selectTab(tab, focus) {
      tabs.forEach(function (t) {
        var on = t === tab;
        t.setAttribute('aria-selected', on);
        t.tabIndex = on ? 0 : -1;
      });
      panel.setAttribute('aria-labelledby', tab.id);
      type = tab.dataset.ncTab;
      if (focus) tab.focus();
      apply(true);
    }

    tabs.forEach(function (tab, i) {
      tab.addEventListener('click', function () { selectTab(tab); });
      tab.addEventListener('keydown', function (e) {
        var j = e.key === 'ArrowRight' ? i + 1 : e.key === 'ArrowLeft' ? i - 1 : e.key === 'Home' ? 0 : e.key === 'End' ? tabs.length - 1 : null;
        if (j === null) return;
        e.preventDefault();
        selectTab(tabs[(j + tabs.length) % tabs.length], true);
      });
    });
    unreadOnly.addEventListener('change', function () { apply(true); });
    project.addEventListener('change', function () { apply(true); });
    all.addEventListener('change', function () { visible().forEach(function (li) { box(li).checked = all.checked; }); sync(); });
    nc.querySelector('[data-nc-list]').addEventListener('change', sync);
    nc.querySelector('[data-nc-list]').addEventListener('click', function (e) {
      var link = e.target.closest('a');
      if (!link) return;
      e.preventDefault(); // demo links; remove when they point somewhere
      markRead(link.closest('li')); apply(false);
    });
    readBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { markRead(li); box(li).checked = false; });
      apply(false);
      status.textContent = sel.length + ' marked as read';
    });
    archiveBtn.addEventListener('click', function () {
      var sel = selected();
      sel.forEach(function (li) { li.remove(); });
      apply(false);
      status.textContent = sel.length + ' archived';
      (all.disabled ? nc.querySelector('[data-nc-reset]') : all).focus();
    });
    nc.querySelector('[data-nc-reset]').addEventListener('click', function () {
      unreadOnly.checked = false; project.value = '';
      selectTab(tabs[0], true);
    });
    apply(false);
  });
})();

About this set

A full notification centre for an app’s notifications page, with a separate empty state. The header shows a running summary such as 8 notifications, 4 unread and a Settings link. Tabs for All, Mentions and System filter one list and show an unread count each. A toolbar holds a Select all checkbox that turns indeterminate when only some rows are picked, a selected count, Mark as read and Archive buttons that wake up once something is selected, an Unread only switch and a project menu. Each row has its own checkbox, an avatar or system icon, a linked message, an optional quoted mention, a project chip and a time element; unread rows are tinted with an accent bar and a dot. When the tab and filters match nothing, an empty state appears with a button that resets every filter, and a second, static empty state shows the first-run case with a link to settings. Use it as the destination for a bell’s View all link. The tabs follow the ARIA tabs pattern over a single tab panel, and the script keeps state in data attributes and the hidden attribute, so the Bootstrap and Tailwind versions share it unchanged.

What’s included

  • All, Mentions and System tabs with unread counts and arrow-key navigation
  • Select all with an indeterminate state and bulk Mark as read and Archive
  • Unread only switch and a project filter
  • Empty state with a reset button, plus a first-run empty state
  • Bootstrap .nav-underline, .form-check, .form-switch, .form-select and .list-group without its JavaScript

Accessibility

The tabs use role="tablist" and role="tab" with aria-selected and a roving tabindex; Left and Right arrows, Home and End move between them. Every row checkbox is named after its notification, for example Select: Kai Brennan commented on Pricing table, unread rows carry visually hidden Unread text, and filter changes, bulk actions and archive counts are announced through a polite status region. After archiving, focus returns to Select all. All controls have a visible focus outline and the switch transition is off under prefers-reduced-motion.

Customise it

In plain CSS change --nc-brand, --nc-ink, --nc-text, --nc-muted and --nc-line on .nc. In Bootstrap the same variables feed the --bs-nav-underline-*, --bs-btn-* and --bs-list-group-* variables; in Tailwind edit the hex values in the aria-selected:, data-[unread]: and accent- utilities.