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'
});
Simplest Notification
<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!');
}
Permission Handling

<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);
Step 3: Show and Control

<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');
1. Success/Error Notifications
<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()
        });
    }
});
2. Integration with Timer

<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();
3. Multiple Notifications with Tracking
<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'
});
4. Silent Notifications
<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'
});
5. Hybrid: Desktop with Toast Fallback (Recommended)

<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'
                });
            }
        }
    }
});
Trigger Notification on Click
<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.

Related Components
<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