Component Deep Dive #33: Notification Dropdown
The difficulty of notification systems isn't the frontend — it's state synchronization: read, unread, count, all three must stay consistent.
Click-Outside-to-Close
Three close paths: toggle on trigger click, close on outside click, close on Escape. e.stopPropagation() prevents the event from bubbling to document — otherwise clicking the trigger opens then immediately closes.
After Escape close, return focus to the trigger — this is an accessibility requirement.
Rendering + Relative Time
function timeAgo(timestamp) {
const diff = Date.now() - timestamp;
if (diff < 60000) return 'Just now';
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
return `${Math.floor(diff / 86400000)}d ago`;
}
Mark as Read
Optimistic Update is best practice for notification systems — update UI first, then sync to server. Users feel "instant marking" instead of waiting for network spinners. On failure, silently roll back — notification marking isn't a critical operation.
Unread Count Sync
Each markAsRead call updates the badge. Badge visibility is controlled by CSS (data-count="0" → display: none), JS only updates the value.
Polling New Notifications
Pause polling when panel is open — user is already viewing notifications, no need to fetch more. Resume when closed.
Common Pitfalls
- Don't forget stopPropagation — otherwise trigger click opens then closes immediately
- Return focus to trigger after Escape — accessibility requirement
- Optimistic update + failure rollback — don't show errors for non-critical operations
- Pause polling when panel is open — no need to fetch while user is viewing
- Relative time needs periodic refresh — "5m ago" should become "1h ago" after an hour
This article was originally published on Deskless Daily.
Top comments (0)