@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/notifyZero 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 variantAction
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):
- Library defaults — factory values documented above
- Global configuration — what you set with
notify.configure() - 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 → WINSBehavior
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, withpointer-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 readersaria-live="polite"(configurable to"assertive") — announces changes without interruptingaria-atomic="true"— screen reader reads the full notificationtabindex="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
