Open demo

CSS code

HTML
<div><span class="lbl">Activity feed</span>
<section class="af" aria-labelledby="af-feed-title">
  <div class="af-head"><h3 class="af-title" id="af-feed-title">Recent activity</h3><span class="af-count">Today</span></div>
  <ol class="af-feed">
    <li><span class="af-av" style="background:#1f5fae" aria-hidden="true">MO</span>
      <div><p><b>Maya Okafor</b> upgraded <a href="#">Fernhill Goods</a> to the Scale plan</p><p class="af-meta"><time datetime="2026-10-16T09:28">12 min ago</time> · Billing</p></div></li>
    <li><span class="af-av" style="background:#0f766e" aria-hidden="true">JB</span>
      <div><p><b>Jonas Berg</b> merged <a href="#">#482 Faster CSV export</a></p><p class="af-meta"><time datetime="2026-10-16T09:02">38 min ago</time> · Deploys</p></div></li>
    <li><span class="af-av" style="background:#9333ea" aria-hidden="true">PN</span>
      <div><p><b>Priya Nair</b> invited 4 teammates to <a href="#">Kestrel Logistics</a></p><p class="af-meta"><time datetime="2026-10-16T08:35">1 h ago</time> · Team</p></div></li>
    <li><span class="af-av" style="background:#c2410c" aria-hidden="true">LM</span>
      <div><p><b>Leo Martins</b> commented on <a href="#">Q4 pricing test</a>: <q>Let’s extend it to the EU stores.</q></p><p class="af-meta"><time datetime="2026-10-16T07:51">2 h ago</time> · Comments</p></div></li>
    <li><span class="af-av" style="background:#475569" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg></span>
      <div><p><b>Automation</b> refunded $84.00 on <a href="#">order #10482</a></p><p class="af-meta"><time datetime="2026-10-16T06:40">3 h ago</time> · Payments</p></div></li>
  </ol>
  <div class="af-foot"><a href="#">View all activity</a></div>
</section></div>

<div><span class="lbl">Notification list with unread state</span>
<section class="af" aria-labelledby="af-note-title" data-notes>
  <div class="af-head"><div><h3 class="af-title" id="af-note-title">Notifications</h3><p class="af-count" aria-live="polite" data-notes-count>3 unread</p></div>
    <button type="button" class="af-btn" data-notes-clear>Mark all as read</button></div>
  <ul class="af-notes">
    <li data-unread><span class="af-ico" style="background:#fee2e2;color:#b91c1c" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20M6 15h4"/></svg></span>
      <div class="af-note-body"><a href="#"><span class="sr-only" data-unread-label>Unread: </span>Payment failed for Larkspur Studio</a><p>Card ending 4242 was declined. We will retry on Oct 18.</p><p class="af-meta"><time datetime="2026-10-16T09:35">5 min ago</time></p></div></li>
    <li data-unread><span class="af-ico" style="background:#ede9fe;color:#6d28d9" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg></span>
      <div class="af-note-body"><a href="#"><span class="sr-only" data-unread-label>Unread: </span>Ana Petrova mentioned you</a><p>“Can you check the churn numbers before Friday?”</p><p class="af-meta"><time datetime="2026-10-16T09:18">22 min ago</time></p></div></li>
    <li data-unread><span class="af-ico" style="background:#dbeafe;color:#1d4ed8" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></span>
      <div class="af-note-body"><a href="#"><span class="sr-only" data-unread-label>Unread: </span>Your export is ready</a><p>orders-sept-2026.csv (2.4 MB) is ready to download.</p><p class="af-meta"><time datetime="2026-10-16T08:40">1 h ago</time></p></div></li>
    <li><span class="af-ico" style="background:#dcfce7;color:#15803d" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg></span>
      <div class="af-note-body"><a href="#">Weekly report sent</a><p>Your Oct 5–11 summary went to 6 recipients.</p><p class="af-meta"><time datetime="2026-10-15T08:00">Yesterday</time></p></div></li>
    <li><span class="af-ico" style="background:#f1f5f9;color:#334155" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span>
      <div class="af-note-body"><a href="#">New sign-in from Lisbon</a><p>Chrome on macOS. Not you? Reset your password.</p><p class="af-meta"><time datetime="2026-10-15T19:12">Yesterday</time></p></div></li>
  </ul>
</section></div>

<div><span class="lbl">Alert feed with severity</span>
<section class="af" aria-labelledby="af-alert-title">
  <div class="af-head"><h3 class="af-title" id="af-alert-title">Ops alerts</h3><span class="af-count">Last 2 hours</span></div>
  <ul class="af-alerts">
    <li><span class="af-sev is-crit"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>Critical</span>
      <p>p95 latency above 800 ms on checkout-api</p><p class="af-meta">eu-west-1 · <time datetime="2026-10-16T09:41">09:41</time></p></li>
    <li><span class="af-sev is-warn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>Warning</span>
      <p>Disk 85% full on db-replica-2</p><p class="af-meta">us-east-1 · <time datetime="2026-10-16T09:12">09:12</time></p></li>
    <li><span class="af-sev is-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>Resolved</span>
      <p>Webhook delivery errors back to normal</p><p class="af-meta">All regions · <time datetime="2026-10-16T08:55">08:55</time></p></li>
    <li><span class="af-sev is-info"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>Info</span>
      <p>Deploy v4.18.2 finished in 6 min 12 s</p><p class="af-meta">All regions · <time datetime="2026-10-16T08:30">08:30</time></p></li>
  </ul>
  <div class="af-foot"><a href="#">Open incident log</a></div>
</section></div>
CSS
/* Activity feed, notification list and alert feed widgets. Unread state is a dot, bold text and a hidden
   "Unread" word; severity is an icon plus a word. Times are <time> elements with a machine-readable datetime. */
.af{--af-ink:#0f172a;--af-muted:#4b5768;--af-line:#e2e8f0;--af-accent:#1f5fae;--af-unread:#f3f8fe;
  background:#fff;color:var(--af-ink);border:1px solid var(--af-line);border-radius:16px;box-shadow:0 1px 2px rgba(15,23,42,.04),0 8px 24px rgba(15,23,42,.05);min-width:0}
.af-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 18px;border-bottom:1px solid var(--af-line)}
.af-title{font-size:1rem;font-weight:700}
.af-count{font-size:.78rem;color:var(--af-muted);font-weight:500}
.af a{color:var(--af-accent);text-decoration:none;font-weight:600;border-radius:4px}
.af a:hover{text-decoration:underline}
.af a:focus-visible,.af-btn:focus-visible{outline:2px solid var(--af-accent);outline-offset:2px}
.af-foot{padding:12px 18px;border-top:1px solid var(--af-line);font-size:.84rem}
.af-meta{font-size:.76rem;color:var(--af-muted)}
.af-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;font-size:.78rem;font-weight:700;color:#fff;position:relative;z-index:1;box-shadow:0 0 0 3px #fff}
/* activity feed */
.af-feed{list-style:none;padding:8px 18px}
.af-feed li{position:relative;display:flex;gap:12px;padding:12px 0}
.af-feed li:not(:last-child)::before{content:"";position:absolute;left:17px;top:48px;bottom:-12px;width:2px;background:var(--af-line)}
.af-feed p{font-size:.88rem}
.af-feed q{color:var(--af-muted)}
/* notifications */
.af-btn{font:inherit;font-size:.78rem;font-weight:600;color:var(--af-accent);background:none;border:0;border-radius:6px;padding:4px 6px;cursor:pointer}
.af-btn:hover:not(:disabled){text-decoration:underline}
.af-btn:disabled{color:var(--af-muted);cursor:default}
.af-notes{list-style:none}
.af-notes li{display:flex;gap:12px;padding:14px 18px;border-bottom:1px solid var(--af-line);position:relative}
.af-notes li:last-child{border-bottom:0}
.af-notes li[data-unread]{background:var(--af-unread)}
.af-notes li[data-unread]::after{content:"";position:absolute;right:18px;top:20px;width:9px;height:9px;border-radius:50%;background:var(--af-accent)}
.af-ico{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:10px}
.af-ico svg{width:18px;height:18px}
.af-note-body{min-width:0;padding-right:18px;display:flex;flex-direction:column;gap:1px}
.af-notes a{font-weight:500;color:var(--af-ink)}
.af-notes li[data-unread] a{font-weight:700}
.af-notes p{font-size:.84rem;color:var(--af-muted)}
/* alert feed */
.af-alerts{list-style:none;padding:6px 0}
.af-alerts li{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;padding:12px 18px;align-items:start}
.af-alerts li+li{border-top:1px solid var(--af-line)}
.af-sev{display:inline-flex;align-items:center;gap:5px;padding:2px 8px 2px 6px;border-radius:6px;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;min-width:96px}
.af-sev svg{width:13px;height:13px;flex:none}
.af-sev.is-crit{color:#991b1b;background:#fee2e2}
.af-sev.is-warn{color:#92400e;background:#fef3c7}
.af-sev.is-ok{color:#166534;background:#dcfce7}
.af-sev.is-info{color:#1e40af;background:#dbeafe}
.af-alerts p{font-size:.88rem;font-weight:600}
.af-alerts .af-meta{grid-column:2}
.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}
@media (max-width:420px){.af-alerts li{grid-template-columns:1fr}.af-alerts .af-meta{grid-column:1}.af-sev{justify-self:start;min-width:0}}
JavaScript
// "Mark all as read": clears the unread state, the hidden "Unread:" words and the count, then disables itself.
document.querySelectorAll('[data-notes]').forEach(box => {
  const btn = box.querySelector('[data-notes-clear]'), count = box.querySelector('[data-notes-count]');
  if (!btn) return;
  btn.addEventListener('click', () => {
    box.querySelectorAll('[data-unread]').forEach(li => li.removeAttribute('data-unread'));
    box.querySelectorAll('[data-unread-label]').forEach(s => s.remove());
    if (count) count.textContent = 'All caught up';
    btn.disabled = true;
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Activity feed</span>
<section class="card af" aria-labelledby="af-feed-title">
  <div class="card-header d-flex justify-content-between align-items-center gap-3"><h3 class="af-title mb-0" id="af-feed-title">Recent activity</h3><span class="af-count">Today</span></div>
  <ol class="af-feed list-unstyled mb-0">
    <li class="d-flex gap-3"><span class="af-av" style="background:#1f5fae" aria-hidden="true">MO</span>
      <div><p class="mb-0"><b>Maya Okafor</b> upgraded <a href="#">Fernhill Goods</a> to the Scale plan</p><p class="af-meta mb-0"><time datetime="2026-10-16T09:28">12 min ago</time> · Billing</p></div></li>
    <li class="d-flex gap-3"><span class="af-av" style="background:#0f766e" aria-hidden="true">JB</span>
      <div><p class="mb-0"><b>Jonas Berg</b> merged <a href="#">#482 Faster CSV export</a></p><p class="af-meta mb-0"><time datetime="2026-10-16T09:02">38 min ago</time> · Deploys</p></div></li>
    <li class="d-flex gap-3"><span class="af-av" style="background:#9333ea" aria-hidden="true">PN</span>
      <div><p class="mb-0"><b>Priya Nair</b> invited 4 teammates to <a href="#">Kestrel Logistics</a></p><p class="af-meta mb-0"><time datetime="2026-10-16T08:35">1 h ago</time> · Team</p></div></li>
    <li class="d-flex gap-3"><span class="af-av" style="background:#c2410c" aria-hidden="true">LM</span>
      <div><p class="mb-0"><b>Leo Martins</b> commented on <a href="#">Q4 pricing test</a>: <q>Let’s extend it to the EU stores.</q></p><p class="af-meta mb-0"><time datetime="2026-10-16T07:51">2 h ago</time> · Comments</p></div></li>
    <li class="d-flex gap-3"><span class="af-av" style="background:#475569" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg></span>
      <div><p class="mb-0"><b>Automation</b> refunded $84.00 on <a href="#">order #10482</a></p><p class="af-meta mb-0"><time datetime="2026-10-16T06:40">3 h ago</time> · Payments</p></div></li>
  </ol>
  <div class="card-footer"><a href="#">View all activity</a></div>
</section></div>

<div><span class="lbl">Notification list with unread state</span>
<section class="card af" aria-labelledby="af-note-title" data-notes>
  <div class="card-header d-flex justify-content-between align-items-center gap-3"><div><h3 class="af-title mb-0" id="af-note-title">Notifications</h3><p class="af-count mb-0" aria-live="polite" data-notes-count>3 unread</p></div>
    <button type="button" class="btn btn-link" data-notes-clear>Mark all as read</button></div>
  <ul class="list-group list-group-flush af-notes rounded-bottom-4">
    <li class="list-group-item d-flex gap-3" data-unread><span class="af-ico" style="background:#fee2e2;color:#b91c1c" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20M6 15h4"/></svg></span>
      <div class="af-note-body"><a href="#" class="stretched-link"><span class="visually-hidden" data-unread-label>Unread: </span>Payment failed for Larkspur Studio</a><p class="mb-0">Card ending 4242 was declined. We will retry on Oct 18.</p><p class="af-meta mb-0"><time datetime="2026-10-16T09:35">5 min ago</time></p></div></li>
    <li class="list-group-item d-flex gap-3" data-unread><span class="af-ico" style="background:#ede9fe;color:#6d28d9" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg></span>
      <div class="af-note-body"><a href="#" class="stretched-link"><span class="visually-hidden" data-unread-label>Unread: </span>Ana Petrova mentioned you</a><p class="mb-0">“Can you check the churn numbers before Friday?”</p><p class="af-meta mb-0"><time datetime="2026-10-16T09:18">22 min ago</time></p></div></li>
    <li class="list-group-item d-flex gap-3" data-unread><span class="af-ico" style="background:#dbeafe;color:#1d4ed8" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></span>
      <div class="af-note-body"><a href="#" class="stretched-link"><span class="visually-hidden" data-unread-label>Unread: </span>Your export is ready</a><p class="mb-0">orders-sept-2026.csv (2.4 MB) is ready to download.</p><p class="af-meta mb-0"><time datetime="2026-10-16T08:40">1 h ago</time></p></div></li>
    <li class="list-group-item d-flex gap-3"><span class="af-ico" style="background:#dcfce7;color:#15803d" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg></span>
      <div class="af-note-body"><a href="#" class="stretched-link">Weekly report sent</a><p class="mb-0">Your Oct 5–11 summary went to 6 recipients.</p><p class="af-meta mb-0"><time datetime="2026-10-15T08:00">Yesterday</time></p></div></li>
    <li class="list-group-item d-flex gap-3"><span class="af-ico" style="background:#f1f5f9;color:#334155" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span>
      <div class="af-note-body"><a href="#" class="stretched-link">New sign-in from Lisbon</a><p class="mb-0">Chrome on macOS. Not you? Reset your password.</p><p class="af-meta mb-0"><time datetime="2026-10-15T19:12">Yesterday</time></p></div></li>
  </ul>
</section></div>

<div><span class="lbl">Alert feed with severity</span>
<section class="card af" aria-labelledby="af-alert-title">
  <div class="card-header d-flex justify-content-between align-items-center gap-3"><h3 class="af-title mb-0" id="af-alert-title">Ops alerts</h3><span class="af-count">Last 2 hours</span></div>
  <ul class="list-group list-group-flush af-alerts">
    <li class="list-group-item"><span class="badge af-sev is-crit"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>Critical</span>
      <p class="mb-0">p95 latency above 800 ms on checkout-api</p><p class="af-meta mb-0">eu-west-1 · <time datetime="2026-10-16T09:41">09:41</time></p></li>
    <li class="list-group-item"><span class="badge af-sev is-warn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>Warning</span>
      <p class="mb-0">Disk 85% full on db-replica-2</p><p class="af-meta mb-0">us-east-1 · <time datetime="2026-10-16T09:12">09:12</time></p></li>
    <li class="list-group-item"><span class="badge af-sev is-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>Resolved</span>
      <p class="mb-0">Webhook delivery errors back to normal</p><p class="af-meta mb-0">All regions · <time datetime="2026-10-16T08:55">08:55</time></p></li>
    <li class="list-group-item"><span class="badge af-sev is-info"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>Info</span>
      <p class="mb-0">Deploy v4.18.2 finished in 6 min 12 s</p><p class="af-meta mb-0">All regions · <time datetime="2026-10-16T08:30">08:30</time></p></li>
  </ul>
  <div class="card-footer"><a href="#">Open incident log</a></div>
</section></div>
CSS
/* Bootstrap .card, .card-header/.card-footer, .list-group and .badge. Unread rows carry data-unread; the script removes it. */
.af{--af-ink:#0f172a;--af-muted:#4b5768;--af-line:#e2e8f0;--af-accent:#1f5fae;--af-unread:#f3f8fe;
  --bs-card-border-color:var(--af-line);--bs-card-border-radius:16px;--bs-card-cap-bg:#fff;--bs-card-cap-padding-y:16px;--bs-card-cap-padding-x:18px;
  color:var(--af-ink);box-shadow:0 1px 2px rgba(15,23,42,.04),0 8px 24px rgba(15,23,42,.05);min-width:0}
.af .list-group{--bs-list-group-border-color:var(--af-line);--bs-list-group-item-padding-y:14px;--bs-list-group-item-padding-x:18px;--bs-list-group-color:var(--af-ink);--bs-list-group-bg:#fff}
.af-alerts.list-group{padding:6px 0}
.af .card-header,.af .card-footer{border-color:var(--af-line)}
.af .card-footer{padding:12px 18px;font-size:.84rem;background:#fff}
.af-title{font-size:1rem;font-weight:700}
.af-count{font-size:.78rem;color:var(--af-muted);font-weight:500}
.af a{color:var(--af-accent);text-decoration:none;font-weight:600;border-radius:4px}
.af a:hover{text-decoration:underline}
.af a:focus-visible,.af .btn:focus-visible{outline:2px solid var(--af-accent);outline-offset:2px;box-shadow:none}
.af .btn-link{--bs-btn-color:var(--af-accent);--bs-btn-hover-color:var(--af-accent);--bs-btn-disabled-color:var(--af-muted);--bs-btn-font-size:.78rem;--bs-btn-font-weight:600;--bs-btn-padding-x:6px;--bs-btn-padding-y:4px;text-decoration:none}
.af .btn-link:hover{text-decoration:underline}
.af-meta{font-size:.76rem;color:var(--af-muted)}
.af-av{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;font-size:.78rem;font-weight:700;color:#fff;position:relative;z-index:1;box-shadow:0 0 0 3px #fff}
.af-feed{padding:8px 18px}
.af-feed li{position:relative;padding:12px 0}
.af-feed li:not(:last-child)::before{content:"";position:absolute;left:17px;top:48px;bottom:-12px;width:2px;background:var(--af-line)}
.af-feed p{font-size:.88rem}
.af-feed q{color:var(--af-muted)}
.af-ico{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:10px}
.af-ico svg{width:18px;height:18px}
.af-note-body{min-width:0;padding-right:18px;display:flex;flex-direction:column;gap:1px}
.af .list-group-item[data-unread]{background:var(--af-unread)}
.af .list-group-item[data-unread]::after{content:"";position:absolute;right:18px;top:20px;width:9px;height:9px;border-radius:50%;background:var(--af-accent)}
.af-notes a{font-weight:500;color:var(--af-ink)}
.af-notes [data-unread] a{font-weight:700}
.af-notes p{font-size:.84rem;color:var(--af-muted)}
.af-notes .af-meta{font-size:.76rem}
.af-alerts .list-group-item{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;padding-top:12px;padding-bottom:12px;align-items:start}
.af-alerts p{font-size:.88rem;font-weight:600}
.af-alerts .af-meta{grid-column:2;font-weight:400}
.af-sev{display:inline-flex;align-items:center;gap:5px;padding:4px 8px 4px 6px;border-radius:6px;font-size:.72rem;letter-spacing:.04em;text-transform:uppercase;min-width:96px}
.af-sev svg{width:13px;height:13px;flex:none}
.af-sev.is-crit{color:#991b1b;background:#fee2e2}
.af-sev.is-warn{color:#92400e;background:#fef3c7}
.af-sev.is-ok{color:#166534;background:#dcfce7}
.af-sev.is-info{color:#1e40af;background:#dbeafe}
@media (max-width:420px){.af-alerts .list-group-item{grid-template-columns:1fr}.af-alerts .af-meta{grid-column:1}.af-sev{justify-self:start;min-width:0}}
JavaScript
// "Mark all as read": clears the unread state, the hidden "Unread:" words and the count, then disables itself.
document.querySelectorAll('[data-notes]').forEach(box => {
  const btn = box.querySelector('[data-notes-clear]'), count = box.querySelector('[data-notes-count]');
  if (!btn) return;
  btn.addEventListener('click', () => {
    box.querySelectorAll('[data-unread]').forEach(li => li.removeAttribute('data-unread'));
    box.querySelectorAll('[data-unread-label]').forEach(s => s.remove());
    if (count) count.textContent = 'All caught up';
    btn.disabled = true;
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5768] uppercase">Activity feed</span>
<section class="min-w-0 rounded-2xl border border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_1px_2px_rgba(15,23,42,.04),0_8px_24px_rgba(15,23,42,.05)]" aria-labelledby="af-feed-title">
  <div class="flex items-center justify-between gap-3 border-b border-[#e2e8f0] px-[18px] py-4"><h3 class="text-base font-bold" id="af-feed-title">Recent activity</h3><span class="text-[.78rem] font-medium text-[#4b5768]">Today</span></div>
  <ol class="px-[18px] py-2">
    <li class="relative flex gap-3 py-3 not-last:before:absolute not-last:before:top-12 not-last:before:-bottom-3 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:bg-[#e2e8f0]"><span class="relative z-[1] grid size-9 flex-none place-items-center rounded-full text-[.78rem] font-bold text-white shadow-[0_0_0_3px_#fff] bg-[#1f5fae]" aria-hidden="true">MO</span>
      <div><p class="text-[.88rem]"><b>Maya Okafor</b> upgraded <a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">Fernhill Goods</a> to the Scale plan</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T09:28">12 min ago</time> · Billing</p></div></li>
    <li class="relative flex gap-3 py-3 not-last:before:absolute not-last:before:top-12 not-last:before:-bottom-3 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:bg-[#e2e8f0]"><span class="relative z-[1] grid size-9 flex-none place-items-center rounded-full text-[.78rem] font-bold text-white shadow-[0_0_0_3px_#fff] bg-[#0f766e]" aria-hidden="true">JB</span>
      <div><p class="text-[.88rem]"><b>Jonas Berg</b> merged <a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">#482 Faster CSV export</a></p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T09:02">38 min ago</time> · Deploys</p></div></li>
    <li class="relative flex gap-3 py-3 not-last:before:absolute not-last:before:top-12 not-last:before:-bottom-3 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:bg-[#e2e8f0]"><span class="relative z-[1] grid size-9 flex-none place-items-center rounded-full text-[.78rem] font-bold text-white shadow-[0_0_0_3px_#fff] bg-[#9333ea]" aria-hidden="true">PN</span>
      <div><p class="text-[.88rem]"><b>Priya Nair</b> invited 4 teammates to <a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">Kestrel Logistics</a></p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T08:35">1 h ago</time> · Team</p></div></li>
    <li class="relative flex gap-3 py-3 not-last:before:absolute not-last:before:top-12 not-last:before:-bottom-3 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:bg-[#e2e8f0]"><span class="relative z-[1] grid size-9 flex-none place-items-center rounded-full text-[.78rem] font-bold text-white shadow-[0_0_0_3px_#fff] bg-[#c2410c]" aria-hidden="true">LM</span>
      <div><p class="text-[.88rem]"><b>Leo Martins</b> commented on <a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">Q4 pricing test</a>: <q class="text-[#4b5768]">Let’s extend it to the EU stores.</q></p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T07:51">2 h ago</time> · Comments</p></div></li>
    <li class="relative flex gap-3 py-3 not-last:before:absolute not-last:before:top-12 not-last:before:-bottom-3 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:bg-[#e2e8f0]"><span class="relative z-[1] grid size-9 flex-none place-items-center rounded-full text-[.78rem] font-bold text-white shadow-[0_0_0_3px_#fff] bg-[#475569]" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg></span>
      <div><p class="text-[.88rem]"><b>Automation</b> refunded $84.00 on <a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">order #10482</a></p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T06:40">3 h ago</time> · Payments</p></div></li>
  </ol>
  <div class="border-t border-[#e2e8f0] px-[18px] py-3 text-[.84rem]"><a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">View all activity</a></div>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5768] uppercase">Notification list with unread state</span>
<section class="min-w-0 rounded-2xl border border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_1px_2px_rgba(15,23,42,.04),0_8px_24px_rgba(15,23,42,.05)] overflow-hidden" aria-labelledby="af-note-title" data-notes>
  <div class="flex items-center justify-between gap-3 border-b border-[#e2e8f0] px-[18px] py-4"><div><h3 class="text-base font-bold" id="af-note-title">Notifications</h3><p class="text-[.78rem] font-medium text-[#4b5768]" aria-live="polite" data-notes-count>3 unread</p></div>
    <button type="button" class="cursor-pointer rounded-md px-1.5 py-1 text-[.78rem] font-semibold text-[#1f5fae] enabled:hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae] disabled:cursor-default disabled:text-[#4b5768]" data-notes-clear>Mark all as read</button></div>
  <ul>
    <li class="group relative flex gap-3 border-b border-[#e2e8f0] px-[18px] py-3.5 last:border-b-0 data-[unread]:bg-[#f3f8fe] data-[unread]:after:absolute data-[unread]:after:top-5 data-[unread]:after:right-[18px] data-[unread]:after:size-[9px] data-[unread]:after:rounded-full data-[unread]:after:bg-[#1f5fae]" data-unread><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#fee2e2] text-[#b91c1c]" aria-hidden="true"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20M6 15h4"/></svg></span>
      <div class="flex min-w-0 flex-col gap-px pr-[18px]"><a href="#" class="rounded font-medium text-[#0f172a] no-underline group-data-[unread]:font-bold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]"><span class="sr-only" data-unread-label>Unread: </span>Payment failed for Larkspur Studio</a><p class="text-[.84rem] text-[#4b5768]">Card ending 4242 was declined. We will retry on Oct 18.</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T09:35">5 min ago</time></p></div></li>
    <li class="group relative flex gap-3 border-b border-[#e2e8f0] px-[18px] py-3.5 last:border-b-0 data-[unread]:bg-[#f3f8fe] data-[unread]:after:absolute data-[unread]:after:top-5 data-[unread]:after:right-[18px] data-[unread]:after:size-[9px] data-[unread]:after:rounded-full data-[unread]:after:bg-[#1f5fae]" data-unread><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#ede9fe] text-[#6d28d9]" aria-hidden="true"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/></svg></span>
      <div class="flex min-w-0 flex-col gap-px pr-[18px]"><a href="#" class="rounded font-medium text-[#0f172a] no-underline group-data-[unread]:font-bold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]"><span class="sr-only" data-unread-label>Unread: </span>Ana Petrova mentioned you</a><p class="text-[.84rem] text-[#4b5768]">“Can you check the churn numbers before Friday?”</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T09:18">22 min ago</time></p></div></li>
    <li class="group relative flex gap-3 border-b border-[#e2e8f0] px-[18px] py-3.5 last:border-b-0 data-[unread]:bg-[#f3f8fe] data-[unread]:after:absolute data-[unread]:after:top-5 data-[unread]:after:right-[18px] data-[unread]:after:size-[9px] data-[unread]:after:rounded-full data-[unread]:after:bg-[#1f5fae]" data-unread><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#dbeafe] text-[#1d4ed8]" aria-hidden="true"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></span>
      <div class="flex min-w-0 flex-col gap-px pr-[18px]"><a href="#" class="rounded font-medium text-[#0f172a] no-underline group-data-[unread]:font-bold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]"><span class="sr-only" data-unread-label>Unread: </span>Your export is ready</a><p class="text-[.84rem] text-[#4b5768]">orders-sept-2026.csv (2.4 MB) is ready to download.</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-16T08:40">1 h ago</time></p></div></li>
    <li class="group relative flex gap-3 border-b border-[#e2e8f0] px-[18px] py-3.5 last:border-b-0 data-[unread]:bg-[#f3f8fe] data-[unread]:after:absolute data-[unread]:after:top-5 data-[unread]:after:right-[18px] data-[unread]:after:size-[9px] data-[unread]:after:rounded-full data-[unread]:after:bg-[#1f5fae]"><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#dcfce7] text-[#15803d]" aria-hidden="true"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg></span>
      <div class="flex min-w-0 flex-col gap-px pr-[18px]"><a href="#" class="rounded font-medium text-[#0f172a] no-underline group-data-[unread]:font-bold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">Weekly report sent</a><p class="text-[.84rem] text-[#4b5768]">Your Oct 5–11 summary went to 6 recipients.</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-15T08:00">Yesterday</time></p></div></li>
    <li class="group relative flex gap-3 border-b border-[#e2e8f0] px-[18px] py-3.5 last:border-b-0 data-[unread]:bg-[#f3f8fe] data-[unread]:after:absolute data-[unread]:after:top-5 data-[unread]:after:right-[18px] data-[unread]:after:size-[9px] data-[unread]:after:rounded-full data-[unread]:after:bg-[#1f5fae]"><span class="grid size-[34px] flex-none place-items-center rounded-[10px] bg-[#f1f5f9] text-[#334155]" aria-hidden="true"><svg class="size-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span>
      <div class="flex min-w-0 flex-col gap-px pr-[18px]"><a href="#" class="rounded font-medium text-[#0f172a] no-underline group-data-[unread]:font-bold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">New sign-in from Lisbon</a><p class="text-[.84rem] text-[#4b5768]">Chrome on macOS. Not you? Reset your password.</p><p class="text-[.76rem] text-[#4b5768]"><time datetime="2026-10-15T19:12">Yesterday</time></p></div></li>
  </ul>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4b5768] uppercase">Alert feed with severity</span>
<section class="min-w-0 rounded-2xl border border-[#e2e8f0] bg-white text-[#0f172a] shadow-[0_1px_2px_rgba(15,23,42,.04),0_8px_24px_rgba(15,23,42,.05)]" aria-labelledby="af-alert-title">
  <div class="flex items-center justify-between gap-3 border-b border-[#e2e8f0] px-[18px] py-4"><h3 class="text-base font-bold" id="af-alert-title">Ops alerts</h3><span class="text-[.78rem] font-medium text-[#4b5768]">Last 2 hours</span></div>
  <ul class="py-1.5">
    <li class="grid grid-cols-1 items-start gap-x-3 gap-y-1 px-[18px] py-3 not-first:border-t not-first:border-[#e2e8f0] min-[421px]:grid-cols-[auto_1fr]"><span class="inline-flex min-w-0 items-center gap-[5px] justify-self-start rounded-md py-0.5 pr-2 pl-1.5 text-[.72rem] font-bold tracking-[.04em] whitespace-nowrap uppercase min-[421px]:min-w-24 text-[#991b1b] bg-[#fee2e2]"><svg class="size-[13px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>Critical</span>
      <p class="text-[.88rem] font-semibold">p95 latency above 800 ms on checkout-api</p><p class="text-[.76rem] text-[#4b5768] min-[421px]:col-start-2">eu-west-1 · <time datetime="2026-10-16T09:41">09:41</time></p></li>
    <li class="grid grid-cols-1 items-start gap-x-3 gap-y-1 px-[18px] py-3 not-first:border-t not-first:border-[#e2e8f0] min-[421px]:grid-cols-[auto_1fr]"><span class="inline-flex min-w-0 items-center gap-[5px] justify-self-start rounded-md py-0.5 pr-2 pl-1.5 text-[.72rem] font-bold tracking-[.04em] whitespace-nowrap uppercase min-[421px]:min-w-24 text-[#92400e] bg-[#fef3c7]"><svg class="size-[13px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>Warning</span>
      <p class="text-[.88rem] font-semibold">Disk 85% full on db-replica-2</p><p class="text-[.76rem] text-[#4b5768] min-[421px]:col-start-2">us-east-1 · <time datetime="2026-10-16T09:12">09:12</time></p></li>
    <li class="grid grid-cols-1 items-start gap-x-3 gap-y-1 px-[18px] py-3 not-first:border-t not-first:border-[#e2e8f0] min-[421px]:grid-cols-[auto_1fr]"><span class="inline-flex min-w-0 items-center gap-[5px] justify-self-start rounded-md py-0.5 pr-2 pl-1.5 text-[.72rem] font-bold tracking-[.04em] whitespace-nowrap uppercase min-[421px]:min-w-24 text-[#166534] bg-[#dcfce7]"><svg class="size-[13px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>Resolved</span>
      <p class="text-[.88rem] font-semibold">Webhook delivery errors back to normal</p><p class="text-[.76rem] text-[#4b5768] min-[421px]:col-start-2">All regions · <time datetime="2026-10-16T08:55">08:55</time></p></li>
    <li class="grid grid-cols-1 items-start gap-x-3 gap-y-1 px-[18px] py-3 not-first:border-t not-first:border-[#e2e8f0] min-[421px]:grid-cols-[auto_1fr]"><span class="inline-flex min-w-0 items-center gap-[5px] justify-self-start rounded-md py-0.5 pr-2 pl-1.5 text-[.72rem] font-bold tracking-[.04em] whitespace-nowrap uppercase min-[421px]:min-w-24 text-[#1e40af] bg-[#dbeafe]"><svg class="size-[13px] flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>Info</span>
      <p class="text-[.88rem] font-semibold">Deploy v4.18.2 finished in 6 min 12 s</p><p class="text-[.76rem] text-[#4b5768] min-[421px]:col-start-2">All regions · <time datetime="2026-10-16T08:30">08:30</time></p></li>
  </ul>
  <div class="border-t border-[#e2e8f0] px-[18px] py-3 text-[.84rem]"><a href="#" class="rounded font-semibold text-[#1f5fae] no-underline hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#1f5fae]">Open incident log</a></div>
</section></div>
JavaScript
// "Mark all as read": clears the unread state, the hidden "Unread:" words and the count, then disables itself.
document.querySelectorAll('[data-notes]').forEach(box => {
  const btn = box.querySelector('[data-notes-clear]'), count = box.querySelector('[data-notes-count]');
  if (!btn) return;
  btn.addEventListener('click', () => {
    box.querySelectorAll('[data-unread]').forEach(li => li.removeAttribute('data-unread'));
    box.querySelectorAll('[data-unread-label]').forEach(s => s.remove());
    if (count) count.textContent = 'All caught up';
    btn.disabled = true;
  });
});

About this set

Three list widgets for what has been happening. The activity feed is a vertical timeline of five events, such as a plan upgrade, a merged pull request and an automated refund, with avatar initials joined by a connector line, links to the objects involved and a relative time with a category. The notification list shows five notifications with tinted icons; the three unread ones have a dot, a pale background, bold titles and a visually hidden Unread label, and a Mark all as read button clears them with a few lines of vanilla JavaScript and updates the count. The alert feed lists ops alerts with severity badges that pair an icon with the words Critical, Warning, Resolved or Info, followed by the region and time. Use the feed for audit trails and team activity, the notification list in a header panel or side column, and the alert feed for monitoring screens. Times are time elements with machine-readable datetime values, and lists are real ol and ul elements.

What’s included

  • Activity timeline with avatars, links and relative times
  • Notification list with a clear unread state
  • Mark all as read button that updates the unread count
  • Severity badges that combine an icon and a word
  • Machine-readable time elements
  • Bootstrap version uses .card, .list-group and .badge

Accessibility

Each widget is a section named by its heading. Unread notifications start with a visually hidden "Unread:" and are bold, so the state does not rely on the blue dot; the unread count sits in a polite live region and updates when the button is used. Links and the button have a visible focus outline.

Customise it

Set --af-accent, --af-unread and --af-line on .af for links, unread rows and dividers, and edit the .af-sev rules for severity colours. In Tailwind change the bg- and text- classes on each row; in Bootstrap the list-group variables are set on .af .list-group.