Tuning the Visuals of a Notification
When a push message arrives, the code that displays it has to fit the user's device and OS. The options controlling appearance are independent from behavioral options, and they vary far more between browsers and platforms than the call itself suggests.
<ServiceWorkerRegistration>.showNotification(<title>, <options>);
Both arguments — title and options — are optional. The option object accepts the fields below, and a compact way to experiment cross-browser is the Notification Generator.
{
"//": "Visual Options",
"body": "<String>",
"icon": "<URL String>",
"image": "<URL String>",
"badge": "<URL String>",
"dir": "<String of 'auto' | 'ltr' | 'rtl'>",
"timestamp": "<Long>"
"//": "Both visual & behavioral options",
"actions": "<Array of Strings>",
"data": "<Anything>",
"//": "Behavioral Options",
"tag": "<String>",
"requireInteraction": "<boolean>",
"renotify": "<Boolean>",
"vibrate": "<Array of Integers>",
"sound": "<URL String>",
"silent": "<Boolean>",
}
Title and body
Without any options, Chrome on Windows labels the notification with the browser name (or the installed PWA's name) and a generic "New notification" body. On Linux, Chrome and Firefox render the same code differently, and long body text is collapsed in Firefox until the user hovers. The same notification also differs between platforms in the same browser because Chrome and Firefox defer to system notification centers; macOS system notifications, for instance, do not support images or action buttons. A Chrome flag (chrome://flags/#enable-system-notifications) set to Disabled switches Chrome to its own custom notifications for all desktop platforms.
Icon and badge
The icon option takes a URL to a small image shown beside the title:
const title = 'Icon Notification';
const options = {
icon: '/images/demos/icon-512x512.png',
};
registration.showNotification(title, options);
There is no cross-platform standard for the size. Android guidelines call for a 64dp image (64px scaled by device pixel ratio); a 192px icon covers a 3x ratio device. The badge serves a different purpose — a small monochrome icon communicating the notification's origin:
const title = 'Badge Notification';
const options = {
badge: '/images/demos/badge-128x128.png',
};
registration.showNotification(title, options);
Only Chrome on Android uses it today; other browsers fall back to showing the browser icon. Android's status bar guidance suggests 24px times device pixel ratio, so 72px or more is the practical floor.
Large image
The image option displays a larger preview:
const title = 'Image Notification';
const options = {
image: '/images/demos/unsplash-farzad-nazifi-1600x1100.jpg',
};
registration.showNotification(title, options);
Rendering ratios differ sharply: Chrome on desktop uses roughly a 4:3 area but doesn't fill the notification's space, while Android crops differently and only guideline is a 450dp width — 1350px or more is a safer bet. Because of the mobile/desktop gap, serving a 4:3 image and letting Android crop is the best current compromise, though the option itself may still change.
Actions and inline replies
Notification actions map to buttons, each with a user-visible title, an icon, and an internal action ID used when the user clicks. Each action also takes a type, defaulting to 'button'. The number the browser will actually show is read from window.Notification?.maxActions:
const maxVisibleActions = window.Notification?.maxActions;
if (maxVisibleActions) {
options.body = `Up to ${maxVisibleActions} notification actions can be displayed.`;
} else {
options.body = 'Notification actions are not supported.';
}
Only Chrome and Opera for Android support actions. The visual treatment is inconsistent across releases: desktop shows color icons; Android 6 and earlier recolors them to the system palette; Android 7 and later hides them entirely. Because desktop Chrome also doesn't anti-alias action icons as well as Android does, icons that look crisp on one platform degrade on another. A 24x24px icon works on desktop but looks wrong on Android; one editor found 128x128px worked well on Android but was poor on desktop.
Practical rules for action icons:
- Keep a consistent color scheme so all icons match.
- Design to work in monochrome since some platforms force it.
- Test sizes on the platforms you target.
- Assume the icon may not display at all.
Inline replies are added with a 'text'-typed action:
const title = 'Alexey Rodionov';
const options = {
body: 'How are you doing? )',
image: '/images/demos/avatar-512x512.jpg',
icon: '/images/demos/icon-512x512.png',
badge: '/images/demos/badge-128x128.png',
actions: [
{
action: 'reply',
type: 'text',
title: 'Reply',
icon: '/images/demos/action-5-128x128.png',
}
],
};
registration.showNotification(title, options);
On Android the reply field appears only after tapping the action; on Chrome for Windows it is always visible. Multiple replies and mixed button/reply layouts are allowed:
const title = 'Poll';
const options = {
body: 'Do you like this photo?',
image: '/images/demos/cat-image.jpg',
icon: '/images/demos/icon-512x512.png',
badge: '/images/demos/badge-128x128.png',
actions: [
{
action: 'yes',
type: 'button',
title: '👍 Yes',
},
{
action: 'no',
type: 'text',
title: '👎 No (explain why)',
placeholder: 'Type your explanation here',
},
],
};
registration.showNotification(title, options);
Direction, vibration, sound, timestamp
The dir parameter accepts auto, ltr, or rtl, and is largely a hint: text direction is generally determined by content. It is intended to guide layout of options like actions, though differences are hard to observe.
Vibration patterns are arrays of alternating vibration and pause durations in milliseconds:
const title = 'Vibrate Notification';
const options = {
// Star Wars shamelessly taken from the awesome Peter Beverloo
// https://tests.peter.sh/notification-generator/
vibrate: [
500, 110, 500, 110, 450, 110, 200, 110, 170, 40, 450, 110, 200, 110, 170,
40, 500,
],
};
registration.showNotification(title, options);
The pattern only runs where vibration hardware and user settings allow it. The sound field expects a URL to play when a notification arrives, but no browser currently implements it:
const title = 'Sound Notification';
const options = {
sound: '/demos/notification-examples/audio/notification-sound.mp3',
};
registration.showNotification(title, options);
Timestamps are Unix epoch values (milliseconds since 1 January 1970 00:00:00 UTC) that tell the platform when the underlying event occurred:
const title = 'Timestamp Notification';
const options = {
body: 'Timestamp is set to "01 Jan 2000 00:00:00".',
timestamp: Date.parse('01 Jan 2000 00:00:00'),
};
registration.showNotification(title, options);
Making the notification useful
The most common UX failure is revealing too little. Because browsers already attach the site's domain, the title and body should not repeat it; they should explain why this notification exists, using the data that triggered it. Instead of "New message / Click here," a title like "John just sent a new message" with a message excerpt in the body does the job in one glance.
Feature detection
Chrome and Firefox currently diverge widely in what notification options they honor. Tests can check the window.Notification prototype:
if ('actions' in window.Notification?.prototype) {
// Action buttons are supported.
} else {
// Action buttons are NOT supported.
}
Replacing 'actions' with any other option name yields support for that feature, letting your display logic adapt rather than assume a capability set.



