Pages Medium
Notifications Page
A full notification center page with tabs for All/Unread/Mentions, grouped by date, mark-as-read actions, and notification type icons. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #f5f6f8;
color: #1a1a2e;
line-height: 1.5;
min-height: 100vh;
}
.notifications-page {
max-width: 720px;
margin: 0 auto;
padding: 24px 16px;
}
/* ── Header ──────────────────────────────────── */
.notifications-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.notifications-header h1 {
font-size: 28px;
font-weight: 700;
color: #1a1a2e;
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
.btn-mark-all {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #fff;
color: #4b5563;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-mark-all:hover {
background: #f9fafb;
border-color: #9ca3af;
color: #1a1a2e;
}
.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #fff;
color: #6b7280;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-icon:hover {
background: #f9fafb;
color: #1a1a2e;
}
/* ── Tabs ────────────────────────────────────── */
.tabs {
display: flex;
border-bottom: 1px solid #e5e7eb;
margin-bottom: 4px;
gap: 4px;
overflow-x: auto;
}
.tab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 16px;
border: none;
border-bottom: 2px solid transparent;
background: none;
color: #6b7280;
font-size: 14px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all 0.15s ease;
}
.tab:hover {
color: #1a1a2e;
}
.tab.active {
color: #4f46e5;
border-bottom-color: #4f46e5;
}
.tab-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 10px;
background: #4f46e5;
color: #fff;
font-size: 11px;
font-weight: 600;
}
.tab-badge:empty,
.tab-badge[data-count="0"] {
display: none;
}
/* ── Date Groups ─────────────────────────────── */
.date-heading {
position: sticky;
top: 0;
z-index: 5;
padding: 12px 16px 8px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #9ca3af;
background: #f5f6f8;
}
/* ── Notification Items ──────────────────────── */
.notification-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
background: #fff;
border-bottom: 1px solid #f0f1f3;
position: relative;
cursor: pointer;
transition: background 0.15s ease;
}
.notification-item:first-of-type {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.date-group:not(:last-child) .notification-item:last-of-type,
.date-group:last-child .notification-item:last-of-type {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
border-bottom: none;
}
.notification-item:hover {
background: #fafbfc;
}
.notification-item.unread {
background: #eff6ff;
}
.notification-item.unread:hover {
background: #e8f1fd;
}
/* ── Unread Dot ──────────────────────────────── */
.unread-dot {
position: absolute;
left: 6px;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 8px;
border-radius: 50%;
background: #4f46e5;
opacity: 0;
transition: opacity 0.2s ease;
}
.notification-item.unread .unread-dot {
opacity: 1;
}
/* ── Notification Icons ──────────────────────── */
.notif-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
}
.notif-icon--comment {
background: #dbeafe;
color: #2563eb;
}
.notif-icon--like {
background: #fee2e2;
color: #dc2626;
}
.notif-icon--mention {
background: #ede9fe;
color: #7c3aed;
}
.notif-icon--system {
background: #f3f4f6;
color: #6b7280;
}
.notif-icon--follow {
background: #d1fae5;
color: #059669;
}
/* ── Notification Content ────────────────────── */
.notif-content {
flex: 1;
min-width: 0;
}
.notif-text {
font-size: 14px;
color: #374151;
line-height: 1.4;
}
.notif-text strong {
font-weight: 600;
color: #1a1a2e;
}
.notif-context {
color: #4f46e5;
font-weight: 500;
}
.notif-excerpt {
font-size: 13px;
color: #6b7280;
margin-top: 4px;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notif-time {
display: inline-block;
font-size: 12px;
color: #9ca3af;
margin-top: 4px;
}
/* ── Action Menu ─────────────────────────────── */
.notif-actions {
position: relative;
flex-shrink: 0;
align-self: center;
}
.btn-action {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
background: transparent;
color: #9ca3af;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-action:hover {
background: #f3f4f6;
color: #4b5563;
}
.action-menu {
display: none;
position: absolute;
right: 0;
top: 100%;
z-index: 20;
min-width: 160px;
padding: 4px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.action-menu.open {
display: block;
}
.action-menu-item {
display: block;
width: 100%;
padding: 8px 12px;
border: none;
border-radius: 6px;
background: none;
color: #374151;
font-size: 13px;
text-align: left;
cursor: pointer;
transition: background 0.1s ease;
}
.action-menu-item:hover {
background: #f3f4f6;
}
.action-menu-item[data-action="delete"] {
color: #dc2626;
}
.action-menu-item[data-action="delete"]:hover {
background: #fef2f2;
}
/* ── Empty State ─────────────────────────────── */
.empty-state {
text-align: center;
padding: 80px 24px;
}
.empty-state-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 96px;
height: 96px;
border-radius: 50%;
background: #f3f4f6;
color: #9ca3af;
margin-bottom: 20px;
}
.empty-state h3 {
font-size: 20px;
font-weight: 600;
color: #1a1a2e;
margin-bottom: 8px;
}
.empty-state p {
font-size: 14px;
color: #6b7280;
}
/* ── Fade-out animation ──────────────────────── */
.notification-item.removing {
opacity: 0;
transform: translateX(40px);
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin: 0;
overflow: hidden;
transition: opacity 0.25s ease, transform 0.25s ease, max-height 0.3s ease 0.15s, padding 0.3s
ease 0.15s;
}
/* ── Responsive ──────────────────────────────── */
@media (max-width: 600px) {
.notifications-page {
padding: 16px 8px;
}
.notifications-header h1 {
font-size: 22px;
}
.btn-mark-all span,
.btn-mark-all svg + span {
display: none;
}
.btn-mark-all {
font-size: 0;
padding: 8px;
}
.btn-mark-all svg {
font-size: initial;
}
.notification-item {
padding: 12px 10px;
gap: 10px;
}
.notif-icon {
width: 34px;
height: 34px;
}
.notif-icon svg {
width: 15px;
height: 15px;
}
.notif-excerpt {
display: none;
}
.tab {
padding: 8px 12px;
font-size: 13px;
}
}(() => {
const list = document.getElementById("notificationsList");
const emptyState = document.getElementById("emptyState");
const markAllBtn = document.getElementById("markAllRead");
const unreadBadge = document.getElementById("unreadBadge");
const tabs = document.querySelectorAll(".tab");
let activeTab = "all";
/* ── Helpers ──────────────────────────────── */
function getItems() {
return Array.from(list.querySelectorAll(".notification-item"));
}
function countUnread() {
return getItems().filter((el) => el.classList.contains("unread")).length;
}
function updateUnreadBadge() {
const count = countUnread();
unreadBadge.textContent = count;
unreadBadge.setAttribute("data-count", count);
if (count === 0) {
unreadBadge.style.display = "none";
} else {
unreadBadge.style.display = "";
}
}
function checkEmpty() {
const visible = getItems().filter(
(el) => !el.classList.contains("removing") && el.style.display !== "none"
);
if (visible.length === 0) {
list.style.display = "none";
emptyState.style.display = "";
} else {
list.style.display = "";
emptyState.style.display = "none";
}
/* Hide date-group headers when all children hidden */
list.querySelectorAll(".date-group").forEach((group) => {
const visibleChildren = Array.from(group.querySelectorAll(".notification-item")).filter(
(el) => !el.classList.contains("removing") && el.style.display !== "none"
);
group.style.display = visibleChildren.length === 0 ? "none" : "";
});
}
/* ── Tab Filtering ────────────────────────── */
function applyTabFilter() {
getItems().forEach((item) => {
const type = item.getAttribute("data-type");
const isUnread = item.classList.contains("unread");
let show = false;
if (activeTab === "all") show = true;
else if (activeTab === "unread") show = isUnread;
else show = type === activeTab;
item.style.display = show ? "" : "none";
});
checkEmpty();
}
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
tabs.forEach((t) => {
t.classList.remove("active");
t.setAttribute("aria-selected", "false");
});
tab.classList.add("active");
tab.setAttribute("aria-selected", "true");
activeTab = tab.getAttribute("data-tab");
closeAllMenus();
applyTabFilter();
});
});
/* ── Mark as Read / Unread ────────────────── */
function markAsRead(item) {
item.classList.remove("unread");
const menuItem = item.querySelector('[data-action="read"]');
if (menuItem) {
menuItem.textContent = "Mark as unread";
menuItem.setAttribute("data-action", "unread");
}
updateUnreadBadge();
if (activeTab === "unread") {
applyTabFilter();
}
}
function markAsUnread(item) {
item.classList.add("unread");
const menuItem = item.querySelector('[data-action="unread"]');
if (menuItem) {
menuItem.textContent = "Mark as read";
menuItem.setAttribute("data-action", "read");
}
updateUnreadBadge();
}
markAllBtn.addEventListener("click", () => {
getItems().forEach((item) => markAsRead(item));
});
/* ── Delete ───────────────────────────────── */
function deleteNotification(item) {
item.style.maxHeight = item.offsetHeight + "px";
/* Force reflow */
void item.offsetHeight;
item.classList.add("removing");
item.addEventListener(
"transitionend",
() => {
item.remove();
updateUnreadBadge();
checkEmpty();
},
{ once: true }
);
}
/* ── Action Menu ──────────────────────────── */
function closeAllMenus() {
document.querySelectorAll(".action-menu.open").forEach((m) => m.classList.remove("open"));
}
list.addEventListener("click", (e) => {
/* Three-dot button */
const actionBtn = e.target.closest(".btn-action");
if (actionBtn) {
e.stopPropagation();
const menu = actionBtn.nextElementSibling;
const isOpen = menu.classList.contains("open");
closeAllMenus();
if (!isOpen) menu.classList.add("open");
return;
}
/* Menu item */
const menuItem = e.target.closest(".action-menu-item");
if (menuItem) {
e.stopPropagation();
const action = menuItem.getAttribute("data-action");
const item = menuItem.closest(".notification-item");
closeAllMenus();
if (action === "read") markAsRead(item);
else if (action === "unread") markAsUnread(item);
else if (action === "delete") deleteNotification(item);
return;
}
/* Clicking the notification row itself marks it read */
const notifItem = e.target.closest(".notification-item");
if (notifItem && notifItem.classList.contains("unread")) {
markAsRead(notifItem);
}
});
/* Close menus on outside click */
document.addEventListener("click", () => closeAllMenus());
/* ── Init ──────────────────────────────────── */
updateUnreadBadge();
checkEmpty();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Notifications</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="notifications-page">
<header class="notifications-header">
<div class="header-left">
<h1>Notifications</h1>
</div>
<div class="header-right">
<button class="btn-mark-all" id="markAllRead">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
Mark all as read
</button>
<button class="btn-icon" aria-label="Settings">
<svg width="20" height="20" 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"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</button>
</div>
</header>
<nav class="tabs" role="tablist">
<button class="tab active" role="tab" data-tab="all" aria-selected="true">All</button>
<button class="tab" role="tab" data-tab="unread" aria-selected="false">Unread <span class="tab-badge" id="unreadBadge">3</span></button>
<button class="tab" role="tab" data-tab="mention" aria-selected="false">Mentions</button>
<button class="tab" role="tab" data-tab="system" aria-selected="false">System</button>
</nav>
<div class="notifications-list" id="notificationsList">
<div class="date-group" data-group="today">
<h2 class="date-heading">Today</h2>
<div class="notification-item unread" data-type="comment" data-id="1">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--comment">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Sarah Chen</strong> commented on your post <span class="notif-context">"Getting started with design tokens"</span></p>
<p class="notif-excerpt">"This is exactly what I was looking for! Great breakdown of the workflow."</p>
<span class="notif-time">2 hours ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="read">Mark as read</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item unread" data-type="like" data-id="2">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--like">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Alex Rivera</strong> liked your photo</p>
<span class="notif-time">4 hours ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="read">Mark as read</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item unread" data-type="system" data-id="3">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--system">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>System</strong> — Your subscription renews tomorrow</p>
<p class="notif-excerpt">Your Pro plan ($12/mo) will automatically renew on March 21, 2026.</p>
<span class="notif-time">5 hours ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="read">Mark as read</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
</div>
<div class="date-group" data-group="yesterday">
<h2 class="date-heading">Yesterday</h2>
<div class="notification-item" data-type="mention" data-id="4">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--mention">
<svg width="18" height="18" 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"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>David Park</strong> mentioned you in <span class="notif-context">#design</span></p>
<p class="notif-excerpt">"@you What do you think about the new color palette? I'd love your input."</p>
<span class="notif-time">1 day ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item" data-type="follow" data-id="5">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--follow">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Emma Wilson</strong> started following you</p>
<span class="notif-time">1 day ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item" data-type="comment" data-id="6">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--comment">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>James Lee</strong> replied to your comment on <span class="notif-context">"Component architecture best practices"</span></p>
<p class="notif-excerpt">"Agreed, composition over inheritance makes so much more sense for UI."</p>
<span class="notif-time">1 day ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
</div>
<div class="date-group" data-group="week">
<h2 class="date-heading">This Week</h2>
<div class="notification-item" data-type="like" data-id="7">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--like">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Mia Thompson</strong> liked your post <span class="notif-context">"Responsive layout patterns"</span></p>
<span class="notif-time">3 days ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item" data-type="system" data-id="8">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--system">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>System</strong> — Security update: New login from Chrome on macOS</p>
<p class="notif-excerpt">If this wasn't you, please review your account security settings immediately.</p>
<span class="notif-time">4 days ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item" data-type="mention" data-id="9">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--mention">
<svg width="18" height="18" 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"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Olivia Martinez</strong> mentioned you in <span class="notif-context">#frontend</span></p>
<p class="notif-excerpt">"@you Can you review the PR for the navigation refactor?"</p>
<span class="notif-time">5 days ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
</div>
<div class="date-group" data-group="older">
<h2 class="date-heading">Older</h2>
<div class="notification-item" data-type="follow" data-id="10">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--follow">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Noah Garcia</strong> started following you</p>
<span class="notif-time">2 weeks ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
<div class="notification-item" data-type="comment" data-id="11">
<div class="unread-dot"></div>
<div class="notif-icon notif-icon--comment">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
</div>
<div class="notif-content">
<p class="notif-text"><strong>Sophia Kim</strong> commented on your post <span class="notif-context">"CSS Grid deep dive"</span></p>
<p class="notif-excerpt">"The subgrid section was incredibly helpful, thanks for writing this!"</p>
<span class="notif-time">3 weeks ago</span>
</div>
<div class="notif-actions">
<button class="btn-action" aria-label="More actions">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"></circle><circle cx="12" cy="12" r="2"></circle><circle cx="12" cy="19" r="2"></circle></svg>
</button>
<div class="action-menu">
<button class="action-menu-item" data-action="unread">Mark as unread</button>
<button class="action-menu-item" data-action="delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
<div class="empty-state" id="emptyState" style="display: none;">
<div class="empty-state-icon">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>
</div>
<h3>All caught up!</h3>
<p>You have no notifications to show here.</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Notifications Page
A full-page notification center with tabbed filtering, grouped by date, individual and bulk actions, and distinct notification type styling.
Features
- Tab filters — All, Unread (count badge), Mentions, System tabs
- Date grouping — Today, Yesterday, This Week, Older sections
- Notification types — Comment, Like, Mention, System, Follow — each with unique icon and color
- Actions — Mark as read, mark all as read, delete individual
- Unread indicator — blue dot for unread items
- Empty state — “All caught up!” when no notifications
When to use it
- App notification center
- Social platform activity feed
- Internal messaging / alert system