poptjs
v2.1.0
Published
Beautiful toast notifications with pill-to-card animations, spring physics, and zero dependencies.
Maintainers
Readme
popt
Beautiful toast notifications with pill-to-card animations, spring physics, and zero dependencies.
~13 KB min JS · ~8 KB min CSS · Zero dependencies · TypeScript ready · llms.txt
Install
npm install poptjs// ESM
import popt from 'poptjs';
import 'poptjs/dist/popt.css';
// UMD / CDN
<script src="https://unpkg.com/poptjs/dist/popt.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/poptjs/dist/popt.min.css">Quick Start
// Simple pill notification
popt.success('Event saved!');
// Expanded card with description
popt.error({
title: 'Payment declined',
description: 'Card ending in 4242 was declined.'
});API
Methods
| Method | Description |
|--------|-------------|
| popt.success(opts) | Green checkmark. Confirmations, saves. |
| popt.error(opts) | Red X. Failures, declined payments. |
| popt.warning(opts) | Orange alert. Low inventory, expiring sessions. |
| popt.info(opts) | Blue info. Updates, announcements. |
| popt.action(opts) | Purple arrow. Includes action button. |
| popt.show(opts) | Gray default. Custom icon optional. |
| popt.confirm(opts) | Two-button confirmation. Returns Promise<boolean>. |
| popt.prompt(opts) | Input field with submit/cancel. Returns Promise<string\|null>. |
| popt.promise(promise, handlers) | Loading → success/error transition. |
| popt.loading(opts) | Spinner toast. Persistent unless given a duration. |
| popt.update(id, state?, opts) | Change a toast that is already on screen. |
| popt.dismiss(id?) | Dismiss that toast, or the most recent one. |
| popt.dismissAll() | Dismiss all toasts. |
| popt.isActive() | Returns true if any toast is visible. |
| popt.getStackSize() | Returns the number of active toasts. |
| popt.getQueueSize() | Returns the number of toasts waiting for a slot. |
| popt.init(config) | Set global defaults. |
Every method accepts a string (title-only pill) or an options object.
Toast Options
| Property | Type | Description |
|----------|------|-------------|
| title | string | Text shown in the pill header |
| description | string \| HTMLElement | Expanded card body content |
| duration | number \| null | Auto-dismiss ms. null = persistent |
| button | { title, onClick } | Action button inside card |
| cancel | { title, onClick } | Cancel button (shown alongside action) |
| input | { type, placeholder, value } | Input field (used with prompt) |
| icon | string | Custom SVG string for badge |
| fill | string | Custom badge/progress color |
| position | string | Override position for this toast |
| expandDelay | number | Pill → card delay in ms |
| onDismiss | () => void | Callback when toast is dismissed |
Init Options
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| position | string | 'top-center' | Default position |
| duration | number | 6000 | Default auto-dismiss ms |
| expandDelay | number | 1000 | Pill → card delay |
| theme | 'dark' \| 'light' \| 'auto' | 'dark' | Color theme |
| maxToasts | number | 3 | Max visible toasts |
| overflow | 'dismiss' \| 'queue' | 'dismiss' | Drop the oldest toast when full, or queue the new one |
Positions
top-left · top-center (default) · top-right · bottom-left · bottom-center · bottom-right
Keyboard
| Key | Action |
|-----|--------|
| Escape | Dismisses the focused toast, or the most recent one |
| Enter | Submits a prompt() from inside its input |
| Tab / Shift+Tab | Cycles through the buttons of a confirm, prompt or action toast |
Toasts with buttons or an input take focus when they expand and hand it back to the previously focused element when they close. Plain toasts never steal focus.
Examples
Action Button
popt.action({
title: 'Booking saved as draft',
description: 'Resume checkout anytime.',
button: {
title: 'Resume checkout',
onClick: () => window.location = '/checkout'
}
});Confirm Dialog
const accepted = await popt.confirm({
title: 'Delete item?',
description: 'This action cannot be undone.',
confirmText: 'Delete',
cancelText: 'Keep it'
});
if (accepted) {
// user confirmed
}Returns false if cancelled, dismissed, or closed.
Prompt
const name = await popt.prompt({
title: 'Rename event',
description: 'Enter a new name for this event.',
placeholder: 'Event name...',
submitText: 'Rename'
});
if (name !== null) {
// user submitted a value
}Returns null if cancelled or dismissed.
Async Promise
popt.promise(fetch('/api/booking'), {
loading: 'Processing booking...',
success: 'Booking confirmed!',
error: 'Something went wrong'
});
// Dynamic messages
popt.promise(fetch('/api/tickets'), {
loading: 'Loading...',
success: (data) => ({
title: 'Loaded!',
description: `${data.count} tickets found`
}),
error: (err) => ({
title: 'Failed',
description: err.message
})
});Themes
// Dark (default)
popt.init({ theme: 'dark' });
// Light
popt.init({ theme: 'light' });
// Auto (follows system preference)
popt.init({ theme: 'auto' });Toast Stacking
Multiple toasts stack visually (up to maxToasts). Oldest are dismissed when the limit is reached.
popt.init({ maxToasts: 5 });
popt.success('First');
popt.info('Second');
popt.warning('Third');With overflow: 'queue' nothing is dropped: extra toasts wait for a free slot and show up as
soon as one opens. The id is returned right away, so a queued toast can be dismissed before it
is ever shown.
popt.init({ maxToasts: 3, overflow: 'queue' });Updating a Toast
Every method returns the toast id. update() changes the toast in place, dismiss(id) closes it.
const id = popt.loading('Uploading files...');
await upload();
popt.update(id, 'success', {
title: 'Upload complete',
description: '3 files uploaded.'
});update() and dismiss() return false if the id is unknown.
Custom Configuration
popt.init({
position: 'bottom-right',
duration: 4000,
expandDelay: 800,
theme: 'light',
maxToasts: 5
});Custom Color
popt.show({
title: 'Custom notification',
description: 'With a custom color.',
fill: '#e040fb'
});Web Components
popt-element.js registers <popt-toaster> and <popt-toast> on top of the same API. It needs
popt.js and popt.css loaded first and does not duplicate the core.
<link rel="stylesheet" href="https://unpkg.com/poptjs/dist/popt.min.css">
<script src="https://unpkg.com/poptjs/dist/popt.min.js"></script>
<script src="https://unpkg.com/poptjs/dist/popt-element.min.js"></script>// or, with a bundler
import 'poptjs/element';<!-- Global config. Same options as popt.init() -->
<popt-toaster theme="light" position="bottom-right" max-toasts="5" overflow="queue"></popt-toaster>
<!-- Fires as soon as it lands in the DOM, removes itself once dismissed -->
<popt-toast type="success" action="Undo">Event saved!</popt-toast><popt-toaster>
Attributes: position, theme, duration, expand-delay, max-toasts, overflow.
Changing any of them re-applies the config.
<popt-toast>
| Attribute | Description |
|-----------|-------------|
| type | success, error, warning, info, action, loading, show. Default info |
| title | Pill text. Falls back to the element's text content |
| description | Card body |
| duration | Auto-dismiss ms, or none to keep it open |
| action | Label of the action button. Clicking it emits popt-action |
| position, expand-delay, icon, fill | Same meaning as the JS options |
| manual | Do not fire on connect. Call .show() yourself |
| keep | Stay in the DOM after being dismissed |
Events: popt-show (with detail.id), popt-action, popt-dismiss. All bubble.
const el = document.createElement('popt-toast');
el.setAttribute('type', 'error');
el.setAttribute('title', 'Payment declined');
el.addEventListener('popt-dismiss', () => console.log('gone'));
document.body.appendChild(el);Removing the element from the DOM dismisses its toast.
Features
- Pill → Card animation — Starts as a compact pill, morphs into an expanded card
- Spring physics — Natural-feeling
linear()easing - Toast stacking — Multiple toasts with configurable limit
- Themes — Dark, light, and auto (system preference)
- Swipe to dismiss — Pointer events: mouse drag, touch and pen
- Pause on hover — Timer pauses when user interacts
- Progress bar — Visual countdown synced with dismiss timer
- Promise support — Loading → success/error transitions
- Confirm dialogs — Two-button accept/cancel with async/await
- Prompt input — Text input with submit/cancel, returns Promise
- Action buttons — Interactive CTAs inside cards
- 6 positions — Any corner or center, top/bottom
- Safe area support — Respects mobile notch
- Reduced motion — Respects
prefers-reduced-motion - Keyboard —
Escapeto dismiss,Enterto submit, focus trap and focus restore - Accessible —
status/alert/alertdialogroles, assertive live region for errors - Web Components —
<popt-toaster>and<popt-toast>custom elements - Zero dependencies — Vanilla JS, no framework needed
- ESM + UMD — Works with bundlers and CDN
- TypeScript definitions — Full IntelliSense support
Browser Support
All modern browsers (Chrome, Firefox, Safari, Edge). The linear() easing function requires recent browser versions.
License
MIT
