Desktop Notification
Browser native desktop notifications with permission handling and event callbacks
Overview
DesktopNotification provides a clean API wrapper around the browser's native Notification API, enabling desktop push notifications with proper permission handling, event callbacks, and instance management.
⚠️ Browser Requirements: Desktop notifications require: User permission (click "Request Permission" button below) Secure context (HTTPS or localhost) User interaction to trigger (cannot auto-show on page load) Browser support (not available in all browsers) Not seeing popups? Notifications may be going to your system notification center (Windows Action Center, macOS Notification Center, etc.). Check your system tray or notification icon.
💡 Alternative: If desktop notifications aren't working for you, consider using Toast notifications instead - they appear directly in the page and don't require permissions.
Key Features
- Browser native desktop notifications
- Automatic permission request handling
- Instance tracking with closeAll() capability
- Full event handler support (onClick, onClose, onError, onShow)
- Persistent notifications with requireInteraction
- Custom icons, badges, and tags
- Silent mode for no-sound notifications
- Graceful browser support detection
When to Use Notifications
- Timer/Alarm Completion: Alert users when countdowns or scheduled events trigger
- Real-time Updates: New messages, comments, or activity notifications
- Task Completion: Background processes, file uploads, long-running operations
- Reminders: Meeting alerts, deadline warnings, scheduled tasks
- System Events: Connection status, errors, important system messages
Basic Usage
// Quick notification (static method)
Domma.elements.notify('Hello', {
body: 'This is a notification!',
icon: '/icon.png'
});
// Instance with full control
const notif = Domma.elements.notification({
title: 'New Message',
body: 'You have 3 unread messages',
requireInteraction: true,
onClick: (event, notificationInstance) => {
console.log('Notification clicked!');
window.focus();
}
});
await notif.show(); // Returns promise
notif.close(); // Close manually
notif.isShown(); // Check state
Quick Start
Show your first desktop notification in seconds. The API automatically handles permission requests.
Simplest Notification
// One-liner for quick notifications
Domma.elements.notify('Hello World!');
// With body text
Domma.elements.notify('Success', {
body: 'Your changes have been saved'
});
<button type="button" class="btn btn-primary" id="quick-demo">Show Quick Notification</button>/**
* Simplest Notification - from public/showcase/elements/notification.
*/
export function mount(root) {
$(root).find('#quick-demo').on('click', async () => {
// Check if notifications are supported
if (!Domma.DesktopNotification.isSupported) {
await E.alert(
'Desktop notifications are not supported in your browser.',
{title: 'Not Supported'}
);
return;
}
// Check permission
if (Domma.DesktopNotification.permission === 'denied') {
await E.alert(
'Notifications are blocked. Please enable them in your browser settings.',
{title: 'Permission Denied'}
);
return;
}
E.notify('Quick Demo', {
body: 'This is a quick notification!',
onError: async (error) => {
await E.alert(
'Failed to show notification. Check browser permissions.',
{title: 'Error'}
);
}
});
});
}Permission Handling
// Check permission status
console.log(Domma.DesktopNotification.permission); // 'default', 'granted', 'denied'
// Request permission manually (optional - auto-requested on first show)
const permission = await Domma.DesktopNotification.requestPermission();
if (permission === 'granted') {
Domma.elements.notify('Permission Granted!');
}
<button type="button" class="btn btn-secondary" id="request-permission">Request Permission</button>
<p id="permission-status" class="mt-2"></p>/**
* Permission Handling - from public/showcase/elements/notification.
*/
export function mount(root) {
// Request Permission
$(root).find('#request-permission').on('click', async () => {
const permission = await Domma.DesktopNotification.requestPermission();
$(root).find('#permission-status').text(`Permission: ${permission}`);
$(root).find('#permission-status').attr('class',
`mt-2 ${permission === 'granted' ? 'text-success' : 'text-danger'}`);
});
}Step-by-Step Tutorial
Learn how to create notifications with full control over behaviour and appearance.
Step 1: Create Notification Instance
const notification = Domma.elements.notification({
title: 'Meeting Reminder',
body: 'Team standup in 5 minutes',
icon: '/assets/calendar-icon.png',
badge: '/assets/badge.png',
tag: 'meeting-reminder', // Reuse tag to replace existing
requireInteraction: true // Stays until user dismisses
});
Step 2: Add Event Handlers
const notification = Domma.elements.notification({
title: 'New Message',
body: 'Click to view',
onClick: (event, notificationInstance) => {
console.log('User clicked notification');
window.focus();
// Navigate to messages page
window.location.href = '/messages';
},
onClose: (event, notificationInstance) => {
console.log('Notification closed');
},
onShow: (event, notificationInstance) => {
console.log('Notification shown');
},
onError: (error) => {
console.error('Notification error:', error);
}
});
Step 3: Show and Control
// Show the notification (async)
await notification.show();
// Check if shown
if (notification.isShown()) {
console.log('Notification is visible');
}
// Close programmatically
setTimeout(() => {
notification.close();
}, 5000);
<button type="button" class="btn btn-primary" id="tutorial-demo">Run Tutorial Demo</button>
<p id="tutorial-status" class="mt-2"></p>/**
* Step 3: Show and Control - from public/showcase/elements/notification.
*/
export function mount(root) {
// Tutorial Demo
$(root).find('#tutorial-demo').on('click', async () => {
const status = $(root).find('#tutorial-status');
status.text('Creating notification...');
const notification = E.notification({
title: 'Tutorial Demo',
body: 'Click this notification to see the onClick handler',
onClick: () => {
status.text('✓ Notification clicked!');
status.attr('class', 'mt-2 text-success');
},
onShow: () => {
status.text('✓ Notification shown (waiting for click)');
},
onClose: () => {
if (status.textContent.includes('waiting')) {
status.textContent = 'Notification closed without clicking';
}
}
});
await notification.show();
});
}Real-World Examples
1. Success/Error Notifications
// Success notification
function notifySuccess(message) {
Domma.elements.notify('Success', {
body: message,
icon: '/icons/success.png',
tag: 'success'
});
}
// Error notification
function notifyError(message) {
Domma.elements.notify('Error', {
body: message,
icon: '/icons/error.png',
tag: 'error',
requireInteraction: true // Don't auto-dismiss errors
});
}
// Usage
notifySuccess('Profile updated successfully');
notifyError('Failed to upload file');
<button type="button" class="btn btn-success" id="example-success">Show Success</button>
<button type="button" class="btn btn-danger" id="example-error">Show Error</button>/**
* 1. Success/Error Notifications - from public/showcase/elements/notification.
*/
export function mount(root) {
// Example: Success/Error
$(root).find('#example-success').on('click', () => {
E.notify('Success', {
body: 'Operation completed successfully!'
});
});
$(root).find('#example-error').on('click', () => {
E.notify('Error', {
body: 'Something went wrong',
requireInteraction: true
});
});
}2. Integration with Timer
// Notify when timer completes
const timer = Domma.elements.timer('#timer', {
duration: 300000, // 5 minutes
onComplete: () => {
Domma.elements.notify('Timer Complete!', {
body: 'Your 5-minute timer has finished',
requireInteraction: true,
onClick: () => window.focus()
});
}
});
<button type="button" class="btn btn-primary" id="example-timer">Start 5-Second Timer</button>
<p id="timer-status" class="mt-2"></p>/**
* 2. Integration with Timer - from public/showcase/elements/notification.
*/
export function mount(root) {
// Example: Timer Integration
$(root).find('#example-timer').on('click', () => {
const status = $(root).find('#timer-status');
status.text('Timer started (5 seconds)...');
const timer = E.timer(null, {
duration: 5000,
onTick: (remaining) => {
status.text(`Timer: ${Math.ceil(remaining / 1000)}s remaining`);
},
onComplete: () => {
status.text('✓ Timer complete! Check your notifications');
E.notify('Timer Complete!', {
body: 'Your 5-second timer has finished',
requireInteraction: true
});
}
});
timer.start();
});
}3. Multiple Notifications with Tracking
// Create multiple notifications
const notifications = [];
for (let i = 0; i < 3; i++) {
const notif = Domma.elements.notification({
title: `Notification ${i + 1}`,
body: `Message content ${i + 1}`,
tag: `notification-${i}` // Unique tag
});
notifications.push(notif);
notif.show();
}
// Close all at once
Domma.DesktopNotification.closeAll();
// Or close specific one
notifications[0].close();
<button type="button" class="btn btn-primary" id="example-multiple">Show 3 Notifications</button>
<button type="button" class="btn btn-secondary" id="example-close-all">Close All</button>/**
* 3. Multiple Notifications with Tracking - from public/showcase/elements/notification.
*/
export function mount(root) {
// Example: Multiple Notifications
let multipleNotifications = [];
$(root).find('#example-multiple').on('click', async () => {
multipleNotifications = [];
for (let i = 0; i < 3; i++) {
const notif = E.notification({
title: `Notification ${i + 1}`,
body: `This is message number ${i + 1}`,
tag: `multi-${i}`
});
multipleNotifications.push(notif);
await notif.show();
}
});
$(root).find('#example-close-all').on('click', () => {
Domma.DesktopNotification.closeAll();
});
}4. Silent Notifications
// Silent notification (no sound)
Domma.elements.notify('Background Task Complete', {
body: 'File has been processed',
silent: true, // No notification sound
tag: 'background-task'
});
<button type="button" class="btn btn-secondary" id="example-silent">Show Silent Notification</button>/**
* 4. Silent Notifications - from public/showcase/elements/notification.
*/
export function mount(root) {
// Example: Silent Notification
$(root).find('#example-silent').on('click', () => {
E.notify('Silent Notification', {
body: 'This notification has no sound',
silent: true
});
});
}5. Hybrid: Desktop with Toast Fallback (Recommended)
// Smart notification that always shows something
async function smartNotify(title, options = {}) {
// Try desktop notification first
if (Domma.DesktopNotification.isSupported &&
Domma.DesktopNotification.permission === 'granted') {
Domma.elements.notify(title, {
body: options.body,
...options.notificationOptions
});
return 'desktop';
}
// Fallback to toast (always visible in page)
const message = options.body || title;
const type = options.type || 'info';
Domma.elements.toast[type](message, {
duration: options.duration || 5000,
position: 'top-right'
});
return 'toast';
}
// Usage - always shows something!
await smartNotify('Task Complete', {
body: 'Your file has been uploaded',
type: 'success'
});
<button type="button" class="btn btn-primary" id="example-hybrid">Show Hybrid Notification</button>
<p id="hybrid-status" class="mt-2"></p>/**
* 5. Hybrid: Desktop with Toast Fallback (Recommended) - from public/showcase/elements/notification.
*/
export function mount(root) {
// Example: Hybrid Notification (Desktop with Toast fallback)
async function smartNotify(title, options = {}) {
// Try desktop notification first
if (Domma.DesktopNotification.isSupported &&
Domma.DesktopNotification.permission === 'granted') {
E.notify(title, {
body: options.body,
...options.notificationOptions
});
return 'desktop';
}
// Fallback to toast (always visible in page)
const message = options.body || title;
const type = options.type || 'info';
E.toast[type](message, {
duration: options.duration || 5000,
position: 'top-right'
});
return 'toast';
}
$(root).find('#example-hybrid').on('click', async () => {
const method = await smartNotify('Hybrid Test', {
body: 'This notification uses the best available method',
type: 'success',
duration: 5000
});
const status = $(root).find('#hybrid-status');
status.text(`✓ Shown using: ${method}`);
status.attr('class', 'mt-2 text-success');
});
}Best Practices
✅ Do's
- Use hybrid approach with Toast fallback - Always provide visual feedback (see example above)
- Request permission at appropriate time - Ask when user performs related action, not on page load
- Provide clear, concise messages - Title should be 1-3 words, body should be one sentence
- Use requireInteraction for important alerts - Errors, critical warnings, time-sensitive actions
- Add onClick handlers for actionable notifications - Navigate user to relevant content
- Use unique tags for related notifications - Replace previous notification of same type
- Check browser support - Use DesktopNotification.isSupported before showing
- Understand system behavior - Notifications may go to system notification center, not always popup
❌ Don'ts
- Don't spam notifications - Batch updates, use debouncing for rapid events
- Don't request permission on page load - Users will likely deny without context
- Don't rely solely on notifications - They may be blocked or unavailable
- Don't use for every action - Reserve for important, time-sensitive events
- Don't use long messages - Keep title + body under 100 characters total
- Don't forget to close - Use tags to auto-replace or close programmatically
Permission UX Pattern
// Good: Explain before requesting
async function setupNotifications() {
// Show in-app prompt first
const userWantsNotifications = await showInAppPrompt(
'Enable notifications to get alerts for timer completion?'
);
if (userWantsNotifications) {
const permission = await Domma.DesktopNotification.requestPermission();
if (permission === 'granted') {
// Show confirmation
Domma.elements.notify('Notifications Enabled!');
} else {
// Show fallback option
Domma.elements.toast('Notifications blocked. You can enable them in settings.');
}
}
}
Error Handling Pattern
async function safeNotify(title, options = {}) {
// Check support
if (!Domma.DesktopNotification.isSupported) {
console.warn('Notifications not supported');
// Fallback to toast or banner
Domma.elements.toast(title);
return null;
}
// Check permission
if (Domma.DesktopNotification.permission !== 'granted') {
console.warn('Notification permission not granted');
Domma.elements.toast(title);
return null;
}
// Show notification with error handler
const notif = Domma.elements.notification({
title,
...options,
onError: (error) => {
console.error('Notification failed:', error);
Domma.elements.toast(title); // Fallback
}
});
await notif.show();
return notif;
}
Complete API Reference
Factory Methods
// Create notification instance
elements.notification(options)
// Quick notification (static method)
elements.notify(title, options)
Options
| Option | Type | Default | Description |
|---|---|---|---|
title |
string | 'Notification' | Notification title (bold text) |
body |
string | '' | Notification body text |
icon |
string | null | URL to icon image |
badge |
string | null | URL to badge image (Android) |
tag |
string | null | Unique ID - replaces existing with same tag |
requireInteraction |
boolean | false | Stays visible until user dismisses |
silent |
boolean | false | No notification sound |
data |
any | null | Custom data attached to notification |
onClick |
function | null | Callback when notification is clicked |
onClose |
function | null | Callback when notification is closed |
onError |
function | null | Callback on error |
onShow |
function | null | Callback when notification is shown |
Instance Methods
// Show notification (returns Promise)
await notification.show()
// Close notification
notification.close()
// Check if shown
notification.isShown() // Returns boolean
Static Methods
// Request permission (returns Promise)
await DesktopNotification.requestPermission()
// Quick notification
DesktopNotification.notify(title, options)
// Close all active notifications
DesktopNotification.closeAll()
Static Properties
// Current permission state
DesktopNotification.permission // 'default', 'granted', 'denied'
// Browser support check
DesktopNotification.isSupported // boolean
Declarative Configuration
While notifications are typically shown programmatically, you can use the config engine to set up notification triggers.
Trigger Notification on Click
$.setup({
'#notify-button': {
events: {
click: (e, $el) => {
Domma.elements.notify('Button Clicked', {
body: 'You clicked the notification button'
});
}
}
}
});
<button type="button" class="btn btn-primary" id="config-demo">Config Engine Demo</button>/**
* Trigger Notification on Click - from public/showcase/elements/notification.
*/
export function mount(root) {
// Config Engine Demo
$.setup({
'#config-demo': {
events: {
click: () => {
E.notify('Config Engine', {
body: 'Triggered via declarative config!'
});
}
}
}
});
}Form Submission Notification
$.setup({
'#contact-form': {
events: {
submit: async (e, $el) => {
e.preventDefault();
// Submit form
const response = await fetch('/api/contact', {
method: 'POST',
body: new FormData(e.target)
});
if (response.ok) {
Domma.elements.notify('Message Sent!', {
body: 'We will get back to you soon',
icon: '/icons/success.png'
});
} else {
Domma.elements.notify('Error', {
body: 'Failed to send message',
requireInteraction: true
});
}
}
}
}
});
CSS Customisation
Desktop notifications are browser-managed and do not use Domma CSS variables.
| Variable | Default | Controls |
|---|---|---|
--dm-primary |
var(--dm-blue-600) |
Icon colour (browser-managed) |
Example Override
/* Desktop notifications are browser-managed */
/* Override via JavaScript options (icon, badge) */
Full CSS Customisation Cheat-Sheet →
Related Components
DesktopNotification works well with other Domma components for complete user feedback systems.
<div class="grid grid-cols-1 grid-cols-md-2 gap-4"><div class="related-card-style">
<h4>Timer</h4>
<p>Countdown timers can trigger notifications on completion</p>
<a href="/showcase/elements/timer" class="btn btn-sm btn-outline">View Timer →</a>
</div>
<div class="related-card-style">
<h4>Alarm</h4>
<p>Scheduled alarms automatically trigger desktop notifications</p>
<a href="/showcase/elements/alarm" class="btn btn-sm btn-outline">View Alarm →</a>
</div>
<div class="related-card-style">
<h4>Toast (Alternative)</h4>
<p>In-app notifications for less critical messages</p>
<a href="/showcase/elements/toast" class="btn btn-sm btn-outline">View Toast →</a>
</div>
<div class="related-card-style">
<h4>Modal</h4>
<p>For important messages requiring immediate attention</p>
<a href="/showcase/elements/modal" class="btn btn-sm btn-outline">View Modal →</a>
</div>
</div>When to Use Each
- DesktopNotification: System-level alerts, background task completion, urgent updates even when tab inactive
- Toast: Quick in-app feedback, success confirmations, non-critical info
- Modal: Requires immediate user decision or attention, blocks interaction
- Alert Banner: Persistent warnings, system status, non-blocking info