JavaScript Notifications Table
| Piece | What it does | Field note |
|---|---|---|
Notification.requestPermission() | Ask once | The grant prompt must come from a user gesture - permission-first UX |
new Notification(title, opts) | Show locally | Works while the page is open; closed-tab pushes need a worker |
serviceWorkerRegistration.showNotification | Push-grade | Fires from the worker when the tab is CLOSED - the PWA path |
onclick on notification | Deep link in | clients.openWindow / focus - notifications are navigation |
tag | Coalesce duplicates | Same tag replaces instead of stacking - chat per-sender dedupe |
requireInteraction | Stay on screen | Persistent until clicked - approvals, alarms (not marketing) |
actions | Inline buttons | Reply/archive without opening - worker-side handlers |
close() / autoclose | Retract politely | Replace-and-close beats notification spam - the unsubscribe driver |
Notifications have two delivery paths with different reach: new Notification() shows only while the page is open, while serviceWorkerRegistration.showNotification fires from the worker even when every tab is closed - the push-grade path that makes installed web apps feel native.
Bottom line: permission is a one-shot trust decision with no easy recovery - browsers bury the re-enable control, so one irrelevant notification costs the channel permanently. Ask in context after demonstrating value, coalesce with tag instead of stacking, and reserve requireInteraction for things people actually requested.
The honest part: notification permission is not like other permissions - it is a RELATIONSHIP. The API is trivially small (ask, show, click); the engineering is entirely product judgment: what deserves interrupting someone's day, on which device, how often, and how gracefully to stop.
How to use
- Ask after value, not on load: show what notifications will contain (a preview, a sample) and put the request behind the button that turns the feature on.
- Deliver through the worker for reach: registration.showNotification(title, { body, tag: senderId }) - it arrives with every tab closed and coalesces per tag.
- Make clicks land somewhere useful: notification.onclick focuses an existing window (clients.matchThis then client.focus()) or opens the deep link - a notification that opens nothing trains users to ignore all of them.
Frequently asked questions
Why must requestPermission happen inside a user gesture?
Trust, mechanically. Browsers only show the permission prompt from a click, keypress or touch - a page-load prompt was the spam vector (sites asking before showing any value), and browsers now auto-DENY those. The deeper design: the prompt is the user spending attention on a promise, so it should fire at the moment of demonstrated intent - right after the user enabled 'remind me' in your UI. The follow-up rule: once denied, the prompt will not re-show; the only recovery is instructing users through browser settings, which is a support-ticket path, not a UX path. Get the ask right the first time or run without the channel.
What is the real difference between new Notification() and showNotification()?
Reach and lifecycle. new Notification() is page-scoped: it works while a tab of your origin is open and focused-enough, dies with the page, and is blocked entirely in some mobile browsers. registration.showNotification() executes in the SERVICE WORKER - it fires with every tab closed (that is how web push delivers), survives browser restarts, and supports the richer options (actions, requireInteraction) on mobile. The rule of thumb: anything part of a push pipeline goes through the worker; quick in-session alerts can use the page path where supported. The worker path is also what iOS-PWA support covers, which makes it the portable default.
What do the tag, renotify and close mechanisms add up to?
Notification hygiene. tag is an identity: a new notification with the same tag REPLACES the old one instead of stacking - chat apps tag per conversation so five messages become one updated notification, not five rows. close() retracts (a 'typewriter joined' notice that self-cancels when they leave); silent replacement keeps the panel clean. The stakes are the permission itself: notification spam is the number-one driver of revocation, and coalescing is the difference between 'informative' and 'app to mute'. Design the notification lifecycle with the same care as the feature it announces.
When are actions and requireInteraction justified?
Only when the notification is itself a task. actions (inline buttons - reply, archive, snooze) shine when responding without opening the app is the point: a calendar invite, a two-factor approval, a message quick-reply. requireInteraction (persistent until dismissed) is for alarms and approvals that must not scroll away - and it is hostile when used for content marketing. Both raise the interrupt cost, so they inherit the same test as notifications generally: did the user ask for THIS at THIS urgency? Everything else - feed updates, 'we miss you', digest nudges - belongs in an in-app inbox, not the system tray.