npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

electron-notify-manager

v1.0.0

Published

In-app toast notifications for Electron using BrowserWindow (not OS native notifications).

Readme

electron-notify-manager

In-app toast notifications for Electron using BrowserWindow (not OS native notifications).

Install

npm i electron-notify-manager
  • Peer dependency: electron >= 13
  • Runtime: main process only (creates BrowserWindow instances)

Quick Start

import { app, BrowserWindow } from 'electron';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

function createMainWindow(): BrowserWindow {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Electron</h1>');
  return win;
}

app
  .whenReady()
  .then(() => {
    createMainWindow();

    notifier = new NotificationManager({ position: 'topRight' });

    notifier.show({
      title: 'Update available',
      description: 'Version 2.1.0 is ready to install',
      variant: 'default',
      duration: 4000,
    });
  })
  .catch((err: unknown) => {
    // eslint-disable-next-line no-console
    console.error(err);
    app.quit();
  });

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

9. Stacking & Queue

NotificationManager keeps a per-display stack ordered newest → oldest. When maxVisible is reached, new notifications are queued (IDs are returned immediately, but no BrowserWindow is created until a slot is available). When a visible notification closes, the manager promotes the next queued notification and then reflows all visible windows to close the gap.

Reflow means recalculating each visible notification’s (x, y) based on the target display’s workArea, position, margin, gap, and the item’s index in the visible stack. Existing windows move with an animation; the newly promoted window animates in from the left or right edge depending on the configured position.

import { app, BrowserWindow } from 'electron';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Stacking demo</h1>');

  notifier = new NotificationManager({
    position: 'bottomRight',
    maxVisible: 3, // show max 3 at once, rest queue
    gap: 10, // px between notifications
    margin: 16, // px from screen edge
  });

  for (let i = 1; i <= 8; i += 1) {
    notifier.show({
      title: `Queued toast #${i}`,
      description: 'Close one and the next will be promoted.',
      variant: i % 2 === 0 ? 'success' : 'default',
      duration: 2500,
    });
  }
});

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

10. Configuration Reference

NotificationManagerOptions passed to new NotificationManager(options).

| Option | Type | Default | Description | |-------------|------------------------|-----------------|--------------------------------------------------| | position | NotificationPosition | 'bottomRight' | Screen position | | width | number | 360 | Window width in px | | height | number | 100 | Window height in px | | margin | number | 16 | Distance from screen edge in px | | gap | number | 10 | Gap between notifications in px | | maxVisible | number | 5 | Max visible at once (rest queue) | | debug | boolean | false | Enable debug logging (reserved for future use) |

Position values (NotificationPosition):

  • topLeft | topCenter | topRight
  • bottomLeft | bottomCenter | bottomRight

11. NotificationOptions Reference

Options passed to notifier.show(options).

| Option | Type | Default | Description | |----------------|---------------------------------------------------|---------------|-----------------------------------------------------------------------------| | title | string | — | Required. Title text | | description | string | — | Required. Body text | | image | string | — | Absolute path to an image file (PNG/JPG/SVG). If omitted, uses built-in icon | | duration | number | 4000 | Duration in ms. 0 disables auto-close | | variant | 'default' \| 'success' \| 'error' \| 'warning' \| 'loading' \| 'progress' | 'default' | Visual style and behavior | | theme | 'dark' \| 'light' \| 'auto' | 'auto' | Theme mode (auto follows system theme) | | progress | number | — | progress variant only. 0–100 | | progressLabel | string | — | progress variant only. Label shown next to progress | | loadingText | string | — | loading variant only. Secondary text | | onClick | () => void | — | Callback invoked when user clicks the notification | | onClose | () => void | — | Callback invoked when the notification is dismissed |

12. Events

NotificationManager is an EventEmitter.

import { app, BrowserWindow } from 'electron';
import { NotificationManager } from 'electron-notify-manager';
import type { CloseReason } from 'electron-notify-manager/dist/types';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Events demo</h1>');

  notifier = new NotificationManager({ position: 'bottomRight' });

  notifier.on('show', (id: string) => {
    // Fired when the notification is actually shown (not when queued).
    // eslint-disable-next-line no-console
    console.log('Notification shown:', id);
  });

  notifier.on('close', (id: string, reason: CloseReason) => {
    // reason: 'duration' | 'user' | 'programmatic' | 'app-quit'
    // eslint-disable-next-line no-console
    console.log('Closed:', id, 'reason:', reason);
  });

  notifier.on('click', (id: string) => {
    // eslint-disable-next-line no-console
    console.log('Clicked:', id);
  });

  notifier.show({
    title: 'Events wired',
    description: 'Click or wait for auto-close.',
    variant: 'default',
    duration: 4000,
  });
});

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

13. Callbacks

Callbacks are configured per-notification via onClick and onClose.

import { app, BrowserWindow, shell } from 'electron';
import path from 'path';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Callbacks demo</h1>');

  notifier = new NotificationManager({ position: 'bottomRight' });

  const filePath = path.join(process.cwd(), 'document.pdf');

  notifier.show({
    title: 'File saved',
    description: 'document.pdf',
    variant: 'success',
    duration: 5000,
    onClick: () => {
      void shell.showItemInFolder(filePath);
    },
    onClose: () => {
      // eslint-disable-next-line no-console
      console.log('notification dismissed');
    },
  });
});

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

14. Custom Image

import { app, BrowserWindow } from 'electron';
import path from 'path';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Image demo</h1>');

  notifier = new NotificationManager({ position: 'bottomRight' });

  notifier.show({
    title: 'TaskEra',
    description: 'New message received',
    image: path.join(__dirname, 'assets', 'icon.png'),
    variant: 'default',
    duration: 5000,
  });
});

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

Note: If no image is provided, the renderer uses a built-in SVG icon for the chosen variant.

15. Sticky Notifications

import { app, BrowserWindow } from 'electron';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Sticky demo</h1>');

  notifier = new NotificationManager({ position: 'bottomRight' });

  // duration: 0 = never auto-closes
  const id = notifier.show({
    title: 'Action required',
    description: 'Please review the pending changes.',
    variant: 'warning',
    duration: 0,
  });

  // Close programmatically when ready
  setTimeout(() => {
    notifier?.close(id);
  }, 4000);
});

app.on('before-quit', () => {
  notifier?.destroy();
  notifier = null;
});

16. Cleanup

import { app, BrowserWindow } from 'electron';
import { NotificationManager } from 'electron-notify-manager';

let notifier: NotificationManager | null = null;

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 900, height: 650 });
  void win.loadURL('data:text/html,<h1>Cleanup demo</h1>');

  notifier = new NotificationManager({ position: 'bottomRight' });
  notifier.show({ title: 'Ready', description: 'App is running', variant: 'default', duration: 2000 });
});

app.on('before-quit', () => {
  notifier?.destroy(); // closes all windows, removes IPC listeners
  notifier = null;
});

17. How It Works

Each notification is its own BrowserWindow, configured as transparent, frameless, and always-on-top so it behaves like an in-app toast rather than a normal window. The main process computes the window coordinates from the target display’s workArea and the configured position, margin, and gap. The renderer controls visuals (icons, theme, and CSS animations), while the main process owns window lifecycle and positioning. The two sides communicate using Electron IPC to handle click/close events, theme updates in auto mode, and reposition requests during stacking changes.

Example app (in this repo)

This package includes a demo under example/.

cd example
npm i
npm start

18. License

MIT