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

@ajosecortes/notify

v1.0.1

Published

A small, dependency-free, framework-agnostic notification library

Readme

Notify

A dependency-free, framework-agnostic TypeScript notification library with a minimal footprint. Built for vanilla browser environments but usable from React, Vue, Svelte, or any web stack.

Installation

npm install @ajosecortes/notify

Zero runtime dependencies. You only need a DOM environment.

Quick Start

import { notify } from '@ajosecortes/notify';

// Simple message
notify('Operation completed');

// With options
notify({
  message: 'File saved',
  duration: 3000,
  variant: 'success',
});

// Variant helpers
notify.success('Success!');
notify.error('Something went wrong');
notify.warning('Be careful');
notify.info('Heads up');

// Global configuration
notify.configure({
  placement: 'top-right',
  maxVisible: 3,
  duration: 5000,
});

API

notify(options)

The main function. Accepts a string or a NotifyOptions object. Returns a NotifyHandle.

const handle = notify({
  message: 'Undo changes?',
  variant: 'info',
  duration: 6000,
  actions: [
    {
      label: 'Undo',
      onClick: (event, handle) => {
        undoChange();
        handle.close();
      },
    },
  ],
  onClose: () => console.log('notification closed'),
});

NotifyOptions

| Field | Type | Default | Description | |---|---|---|---| | message | string | (required) | Notification text | | variant | 'default' \| 'success' \| 'error' \| 'warning' \| 'info' | 'default' | Semantic variant. Sets color, border, and icon | | duration | number | 4000 | Milliseconds before auto-close. 0 = stays open | | actions | Action[] | [] | Action buttons (Undo, Retry, etc.) | | onClose | () => void | — | Callback when the notification closes | | className | string | '' | Additional CSS class on the notification | | showCloseButton | boolean | true | Show the close button (✕) | | animation | boolean | true | Enable entry/exit animations | | pauseOnHover | boolean | true | Pause auto-close on hover | | icon | string \| false | variant's icon | Icon HTML. false to hide | | ariaLive | 'polite' \| 'assertive' | 'polite' | aria-live value for screen readers |

NotifyHandle

Every notify() call returns a handle to control the notification:

const handle = notify('Message');

handle.close();                           // Close the notification
handle.update({ message: 'Edited' });    // Change the message
handle.update({ duration: 10000 });      // Extend the timer
handle.update({ variant: 'error' });     // Change the variant

Action

interface Action {
  label: string;                                          // Button text
  onClick: (event: MouseEvent, handle: NotifyHandle) => void; // Callback
  className?: string;                                     // Extra CSS class
}

Variant helpers

notify.success('Saved');    // Same as notify({ message: '...', variant: 'success' })
notify.error('Failed');     // Same as notify({ message: '...', variant: 'error' })
notify.warning('Heads up'); // Same as notify({ message: '...', variant: 'warning' })
notify.info('Note');        // Same as notify({ message: '...', variant: 'info' })

They accept the same string or options object as notify(), without needing to pass variant.

notify.configure(config)

Sets global configuration. All notifications inherit these values.

notify.configure({
  placement: 'top-left',
  maxVisible: 5,
  duration: 3000,
  animation: true,
  pauseOnHover: true,
  showCloseButton: true,
  className: 'global-class',
  variant: 'default',
});

Pass null to reset all global configuration to factory defaults:

notify.configure(null);

NotifyConfig

| Field | Type | Default | Description | |---|---|---|---| | placement | Placement | 'bottom-right' | Corner where the container appears | | maxVisible | number | 5 | Maximum visible notifications at once | | duration | number | 4000 | Default auto-close time (ms) | | animation | boolean | true | Animate entry and exit | | pauseOnHover | boolean | true | Pause auto-close on hover | | showCloseButton | boolean | true | Show close button | | className | string | '' | Global CSS class for all notifications | | variant | Variant | 'default' | Default variant |

Placement

type Placement =
  | 'top-left'    | 'top-center'    | 'top-right'
  | 'bottom-left' | 'bottom-center' | 'bottom-right';

notify.dismissAll()

Removes all active notifications immediately (no exit animation).

notify.dismissAll();

Configuration Precedence

Every option resolves in this order (last wins):

  1. Library defaults — factory values documented above
  2. Global configuration — what you set with notify.configure()
  3. Per-notification options — what you pass in the notify() call
// Library default: showCloseButton = true
notify.configure({ showCloseButton: false });            // Global: false
notify({ message: 'With X', showCloseButton: true });    // Per-notif: true → WINS

Behavior

Stacking & Overflow

Notifications stack vertically in insertion order. When maxVisible (default 5) is exceeded, the oldest visible notification is removed first. Overflow removal is synchronous (no animation) for predictable behavior.

notify.configure({ maxVisible: 3 });
notify('A');
notify('B');
notify('C');
notify('D'); // Removes 'A' before showing 'D'

Auto-close

Each notification auto-closes after 4 seconds by default. Hovering pauses the timer; it resumes when the mouse leaves. Set duration: 0 to disable auto-close.

notify({ message: 'I stay open', duration: 0 });

Animations

Entry and exit animations are enabled by default. They use @keyframes notify-enter and @keyframes notify-exit which you can override in your CSS.

  • Enter: opacity + translateY + scale, 0.25s ease-out
  • Exit: opacity + translateY + scale + max-height, 0.2s ease-in, with pointer-events: none

Disable globally or per notification:

notify.configure({ animation: false });
// or
notify({ message: 'No animation', animation: false });

Accessibility

Every notification includes:

  • role="status" — semantic role for screen readers
  • aria-live="polite" (configurable to "assertive") — announces changes without interrupting
  • aria-atomic="true" — screen reader reads the full notification
  • tabindex="0" — notification is keyboard-focusable
  • Close button with aria-label="Close notification"
  • Action buttons with type="button" to prevent accidental form submissions

Visual Customization

CSS Variables

The entire appearance is controlled through CSS variables. Override them in your stylesheet:

.notify-notification {
  --notify-bg: #1e1e2e;
  --notify-color: #cdd6f4;
  --notify-border-radius: 12px;
  --notify-padding: 16px 20px;
  --notify-font-family: 'Inter', sans-serif;
  --notify-font-size: 15px;
  --notify-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
  --notify-border: 1px solid rgba(255, 255, 255, 0.1);
  --notify-icon-size: 24px;
  --notify-icon-color: #89b4fa;
  --notify-close-color: #6c7086;
  --notify-close-hover-color: #cdd6f4;
  --notify-action-color: #89b4fa;
  --notify-action-hover-color: #b4befe;
}

.notify-notification--success { --notify-bg: #1e2a1e; --notify-border: 1px solid #a6e3a1; }
.notify-notification--error   { --notify-bg: #2e1e1e; --notify-border: 1px solid #f38ba8; }
.notify-notification--warning { --notify-bg: #2e2a1e; --notify-border: 1px solid #f9e2af; }
.notify-notification--info    { --notify-bg: #1e1e2e; --notify-border: 1px solid #89b4fa; }

CSS Classes

The DOM structure Notify generates:

<div class="notify-container notify-container--bottom-right" data-notify-host>
  <div class="notify-notification notify-notification--error" id="notify-1"
       role="status" aria-live="polite" aria-atomic="true" tabindex="0">
    <span class="notify-notification__icon"><svg>...</svg></span>
    <div class="notify-notification__content">
      <div class="notify-notification__message">Message here</div>
      <div class="notify-notification__actions">
        <button class="notify-notification__action" type="button">Undo</button>
      </div>
    </div>
    <button class="notify-notification__close" type="button" aria-label="Close notification">✕</button>
  </div>
</div>

Target any class to extend styles. Use className to add your own:

notify.configure({ className: 'app-toast' });
notify({ message: 'Hello', className: 'highlight' });

Custom Animations

Override the keyframes to change animations:

@keyframes notify-enter {
  from { opacity: 0; transform: translateX(100%); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes notify-exit {
  from { opacity: 1; transform: translateX(0); max-height: 200px; }
  to   { opacity: 0; transform: translateX(100%); max-height: 0; }
}

Exported Types

import type {
  NotifyConfig,
  NotifyOptions,
  NotifyHandle,
  NotifyFunction,
  Action,
  Variant,
  Placement,
} from '@ajosecortes/notify';

Examples

Notification with actions

const handle = notify({
  message: 'Item deleted',
  variant: 'warning',
  duration: 8000,
  actions: [
    {
      label: 'Undo',
      onClick: (e, h) => {
        restoreItem();
        handle.update({ message: 'Item restored', variant: 'success' });
        setTimeout(() => h.close(), 2000);
      },
    },
  ],
});

Persistent notification with callback

notify({
  message: 'Session about to expire',
  variant: 'warning',
  duration: 0, // never auto-closes
  showCloseButton: false,
  actions: [
    {
      label: 'Extend session',
      onClick: (e, h) => {
        refreshSession();
        h.close();
      },
    },
  ],
});

React integration (no wrapper needed)

import { notify } from '@ajosecortes/notify';
import { useEffect } from 'react';

function App() {
  useEffect(() => {
    notify.configure({ placement: 'top-right', maxVisible: 3 });
    return () => {
      notify.dismissAll();
    };
  }, []);

  const handleSave = async () => {
    try {
      await saveData();
      notify.success('Data saved');
    } catch {
      notify.error('Save failed');
    }
  };

  return <button onClick={handleSave}>Save</button>;
}

Vue integration (no wrapper needed)

import { notify } from '@ajosecortes/notify';

export default {
  mounted() {
    notify.configure({ placement: 'bottom-left' });
  },
  methods: {
    handleDelete() {
      notify({
        message: 'User deleted',
        variant: 'success',
        duration: 5000,
      });
    },
  },
};

Philosophy

Notify is not a UI framework. It's a surgical tool: show, stack, animate, interact with, and dismiss ephemeral notifications. Nothing more.

  • Zero dependencies. No React, no Vue, no Svelte, no external utilities.
  • TypeScript-first. Strict types exported for autocomplete and compile-time safety.
  • CSS-driven. CSS variables for everything visual. No forced utility classes.
  • Agnostic. Works the same in vanilla JS, React, Vue, Svelte, Angular, or any stack.
  • Predictable. Configuration precedence is explicit: per-notification > global > defaults.

License

MIT