A lightweight, framework-agnostic browser notification utility with automatic permission handling, typed notification methods, de-duplication and smart fallbacks.
📋 Live Demo | 📦 NPM Package
- To notify the user about a time-consuming server or client-side task, such as image generation/editing or video generation/editing
- Long-running file uploads or downloads, where the user might leave the tab until completion
- Polling operations that require several minutes to finish
- Any workflow where the user starts a process and may switch tabs, and you want to notify them or bring back their attention when the task completes or fails
- Typed Notification Methods - Pre-configured methods for success, error, info, warning, and general messages
- Simple Configuration - Configure once with
notifier.init()
, use everywhere withnotify.*
- Automatic Permission Handling - Requests notification permission automatically when initialized, handles permission rejection by showing an alert with a configurable message
- Framework Agnostic - Works with any JavaScript framework or vanilla JS
- Full Notification API Support - Supports all native Notification API options
- Deduplication - By default only shows notifications when user is on other tabs, can be changed
- Smart Tag Management - Automatically generates unique tags to prevent notification collisions
- Auto Favicon Detection - Uses the page's favicon as notification icon when none provided
- Smart Fallbacks - Plays beep sound on source tab when notifications are blocked
- TypeScript Support - Fully typed with comprehensive interfaces
- Lightweight - Minimal dependencies and small bundle size
- Browser Compatibility - Works across all modern browsers
npm install @devkit-labs/notifier
import { notifier, notify } from "@devkit-labs/notifier";
// Initialize once at app startup (important!)
notifier.init({
onPermissionDeny: () => {
// show a toast or modal to ask for permission
},
});
// Use anywhere in your app after initialization
notify.success("Hello World!");
That's it! The library will:
- âś… Automatically request notification permission
- âś… Use default icons and settings
- âś… Fall back to audio beep if notifications are blocked/denied
- âś… Only show notifications when user is on other tabs (by default)
// Simple import - everything you need
import { notifier, notify } from "@devkit-labs/notifier";
// Full imports including utilities
import {
notifier,
notify,
getPermissionStatus,
isNotificationSupported,
} from "@devkit-labs/notifier";
// Configure the notifier with optional custom icons and settings,
// Note : All fields are optional, Notifier comes with default icons and settings
const notifierConfig = {
alertSound: "/alert-sound.mp3",
showOnSourceTab: false, // false by default
onPermissionDeny: () => {
console.log("Notification permission denied");
// Handle permission denial (e.g., show a toast message)
},
icons: {
success: "/success-icon.png",
error: "/error-icon.png",
info: "/info-icon.png",
warning: "/warning-icon.png",
},
};
// Initialize with config and request notification permission
notifier.init(notifierConfig); // can be initialized without config too
// notifier.init(); uses default icons & settings
// Use typed notification methods
notify.success("Task completed!");
notify.error("Upload failed!", {
body: "Please check your connection and try again.",
});
notify.warning("Storage almost full", {
body: "Please free up some space.",
});
notify.info("New feature available", {
body: "Check out our latest update.",
});
notify.message("Meeting reminder", {
body: "Team standup starts in 5 minutes.",
}); // uses favicon
Always initialize the notifier only once in your application's entry point (e.g., main.js
, index.js
, app.js
, or close to your root component).
// Initialize once in your main entry file
// main.js or index.js
import { notifier } from "@devkit-labs/notifier";
notifier.init({
icons: {
success: "/icons/success.png",
error: "/icons/error.png",
},
});
// Then use notify.* anywhere in your app after initialization
notify.success("Your task is complete");
// Multiple initializations will overwrite each other
// file1.js
notifier.init({ alertSound: "/sound1.mp3" });
// file2.js
notifier.init({ alertSound: "/sound2.mp3" }); // Overwrites file1's config
Initialize the notifier with optional configuration. This should be called once at the start of your application.
interface NotifierConfig {
alertSound?: string; // Custom alert sound URL for audio fallback (optional)
showOnSourceTab?: boolean; // (default : false) Whether to show the notifications when user is on source tab, recommended way would be to handle it using application UI(toast, modal etc)
onPermissionDeny?: () => void; // Callback function called when permission is denied
icons?: {
// icons path for various type of notifications, if not provided uses default icons for each
success?: string;
error?: string;
info?: string;
warning?: string;
};
}
// Example configuration
notifier.init({
alertSound: "/sounds/notification.mp3",
onPermissionDeny: () => {
// Handle permission denial
console.log("User denied notification permission");
},
icons: {
success: "data:image/svg+xml,<svg>...</svg>",
error: "/icons/error.png",
info: "/icons/info.png",
warning: "/icons/warning.png",
},
});
All notification methods follow the same pattern: notify.{type}(title, options?)
Shows a success notification with the configured success icon.
notify.success("Upload Complete!");
notify.success("File Saved", {
body: "Your document has been saved successfully.",
requireInteraction: true,
onclick: () => window.focus(),
});
Shows an error notification with the configured error icon.
notify.error("Upload Failed!");
notify.error("Connection Error", {
body: "Unable to connect to server.",
requireInteraction: true,
});
Shows an informational notification with the configured info icon.
notify.info("New Feature");
notify.info("Update Available", {
body: "Version 2.0 is now available.",
onclick: () => window.open("/updates"),
});
Shows a warning notification with the configured warning icon.
notify.warning("Low Storage");
notify.warning("Unsaved Changes", {
body: "You have unsaved changes that will be lost.",
requireInteraction: true,
});
Shows a general message notification using the page's favicon (no configured icon).
notify.message("New Message");
notify.message("Meeting Reminder", {
body: "Daily standup starts soon.",
});
All notification methods accept the same options object, which are same as accepted by native Notification api:
interface NotificationOptions {
badge?: string; // URL of badge image
body?: string; // Notification body text
data?: any; // Custom data to attach
dir?: "auto" | "ltr" | "rtl"; // Text direction
image?: string; // URL of notification image
lang?: string; // Language code
onclick?: (event: Event) => void; // Click handler
onclose?: (event: Event) => void; // Close handler
onerror?: (event: Event) => void; // Error handler
onshow?: (event: Event) => void; // Show handler
renotify?: boolean; // Whether to replace previous notifications
requireInteraction?: boolean; // Keep notification visible until user interacts
silent?: boolean; // Whether notification should be silent
timestamp?: number; // Custom timestamp
title?: string; // Alternative title (use parameter instead)
vibrate?: number[]; // Vibration pattern for mobile devices
}
Returns the current notification permission status.
import { getPermissionStatus } from "@devkit-labs/notifier";
const status = getPermissionStatus(); // 'default' | 'granted' | 'denied'
Checks if the browser supports notifications.
import { isNotificationSupported } from "@devkit-labs/notifier";
if (isNotificationSupported()) {
// Notifications are supported
}
import { notifier, notify } from "@devkit-labs/notifier";
// Initialize once
notifier.init({
onPermissionDeny: () => {
// Show app-specific message when permission is denied
showToast(
"Notifications are disabled. Enable them for better experience."
);
},
icons: {
success: "/icons/success.svg",
error: "/icons/error.svg",
},
});
// Use anywhere
notify.success("Task completed!");
notify.error("Something went wrong!");
notify.error("Upload Failed", {
body: "The file could not be uploaded to the server.",
badge: "/icons/badge.png",
image: "/images/error-preview.jpg",
requireInteraction: true,
vibrate: [200, 100, 200],
data: {
fileId: "12345",
fileName: "document.pdf",
},
onclick: () => {
console.log("User clicked on error notification");
window.focus();
},
onclose: () => {
console.log("Error notification was closed");
},
});
// By default it will only show notification if user is NOT on the current tab
notify.success("Background Task Complete", {
body: "Your export has finished.",
onclick: () => {
window.focus();
// Navigate to results page
},
});
// Always show notification
notify.warning("Important Alert", {
body: "This will show regardless of tab visibility.",
showOnSourceTab: true, // Override the default (false)
});
When notification permission is denied, the library automatically:
- Triggers the
onPermissionDeny
callback (if configured) - Plays a beep sound on source tab when a notification is triggered (unless
silent: true
).
// Configure permission denial handler
notifier.init({
onPermissionDeny: () => {
// Show your app's notification (toast, modal, etc.)
showToast("Notifications disabled. Enable for alerts!");
},
alertSound: "/sounds/beep.mp3", // Custom beep sound (default : system sound)
});
// This will trigger onPermissionDeny callback if permission is denied
notify.error("Critical Error", {
body: "This is important information for the user.",
});
// Silent fallback (no beep sound)
notify.info("Silent Update", {
body: "This won't make a sound even in fallback mode.",
silent: true, // No beep sound when permission denied
});
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Full support (with some limitations on mobile)
- Mobile browsers: Support varies by platform
- Initialize once, use everywhere - Call
notifier.init()
only once in your main entry file (index.js, main.js, app.js). Multiple initializations will overwrite previous configurations. - Handle denied permissions - Configure
onPermissionDeny
callback to handle permission rejection gracefully with your app's UI (toast, modal, etc.) - Use appropriate notification types - Use
success
for completions,error
for failures, etc. - Don't spam notifications - Only show notifications for important events that users care about
- Provide meaningful content - Use clear titles and descriptive body text
- Handle clicks appropriately - Focus your app window or navigate to relevant content when notifications are clicked
- Respect user preferences - Check permission status and handle denied permissions gracefully
- Use
showOnSourceTab
wisely - Consider whether users need to see notifications when they're already using your app. In such cases, show notifications using your application UI instead - Test fallback behavior - Ensure your app works well even when notifications are blocked (audio beep + callback)
This package is written in TypeScript and includes full type definitions. No additional @types packages are needed.
// All methods and options are fully typed
import {
notifier,
notify,
NotifierConfig,
NotificationOptions,
} from "@devkit-labs/notifier";
const config: NotifierConfig = {
onPermissionDeny: () => console.log("Permission denied"),
icons: {
success: "/success.png",
},
};
const options: NotificationOptions = {
requireInteraction: true,
};
notify.success("Typed!", options);
MIT
We welcome contributions! If you'd like to help improve this package, please submit a pull request via our GitHub repository.