A framework-free, lightweight toast notification library for JavaScript/TypeScript. Works with vanilla JS, React, Vue, Svelte, Angular, Solid, Preact, Astro, Web Components, and any other frontend framework.
- Installation
- Quick Start
- Toast Types
- API Reference
- Configuration
- Programmatic Control
- Promise Toasts
- Loading State
- Actions
- Queue Management
- Deduplication
- Custom Containers
- Theming
- Accessibility
- SSR Safety
- Legacy API
- Migration Guide
npm install themtfyyarn add themtfypnpm add themtfyTypeScript + CSS imports: If your
tsconfig.jsondoesn't include"vite/client"types and you see an error onimport "themtfy/style.css", add an ambient declaration to your project (e.g.env.d.ts):declare module "*.css";
import { toast } from "themtfy";
import "themtfy/style.css";
toast.success("Saved", "Your changes have been saved.");
toast.error("Error", "Something went wrong.");
toast.warning("Warning", "Please review your input.");
toast.info("Info", "A new version is available.");toast.success("Saved!");toast.success("Saved", "Your changes have been saved.");toast.show({
title: "Uploading",
body: "Please wait...",
type: "info",
autoClose: false,
});toast.success(title, body?)
toast.error(title, body?)
toast.warning(title, body?)
toast.info(title, body?)
toast.loading(title, body?)Each type has an associated icon and color:
| Type | Icon | Color |
|---|---|---|
success |
Checkmark | Green |
error |
X | Red |
warning |
Triangle | Yellow |
info |
Circle-i | Blue |
loading |
Spinner | Blue |
default |
Bell | Gray |
Show a toast notification.
const id = toast.show({
title: "Hello",
body: "World",
type: "info",
position: "top-right",
autoClose: 5000,
canClose: true,
showProgress: false,
pauseOnHover: true,
pauseOnFocus: true,
pauseOnVisibilityChange: true,
dedupeKey: "my-key",
icon: "info", // or false, or HTMLElement
action: {
label: "Undo",
onClick: () => restore(),
},
});Returns: string (toast ID)
Update an existing toast.
toast.update(id, {
title: "Complete",
body: "Upload finished.",
type: "success",
progress: 100,
});Dismiss a specific toast.
toast.dismiss(id);Dismiss all active toasts.
toast.dismissAll();Get the state of a specific toast.
const state = toast.get(id);
// Returns: ToastState | undefinedGet all active toasts.
const states = toast.getAll();
// Returns: ToastState[]Configure global defaults.
toast.configure({
defaults: {
position: "bottom-right",
autoClose: 4000,
showProgress: true,
},
maxToasts: 5,
maxQueue: 20,
theme: "system",
});toast.configure({
defaults: {
position: "top-right",
autoClose: 5000,
canClose: true,
showProgress: false,
pauseOnHover: true,
pauseOnFocus: true,
pauseOnVisibilityChange: true,
},
});For isolated configurations, use createThemtfy():
import { createThemtfy } from "themtfy";
const manager = createThemtfy({
maxToasts: 3,
maxQueue: 10,
defaults: {
position: "bottom-right",
autoClose: 3000,
},
theme: "dark",
container: document.getElementById("my-container"),
});
manager.show({ title: "Hello" });
manager.success("Saved");const id = toast.show({ title: "Hello" });
toast.dismiss(id);const id = toast.show({ title: "Uploading", type: "info" });
// Update after 50%
toast.update(id, {
body: "50% complete",
progress: 50,
});
// Update when done
toast.update(id, {
title: "Complete",
type: "success",
body: "Upload finished.",
progress: 100,
});Pause auto-close on hover, focus, or tab visibility:
toast.show({
title: "Installing",
body: "Please wait...",
pauseOnHover: true,
pauseOnFocus: true,
pauseOnVisibilityChange: true,
});Show loading, success, or error states based on a promise.
const data = await toast.promise(fetch("/api/data"), {
loading: { title: "Loading...", body: "Fetching data..." },
success: (data) => ({
title: "Loaded",
body: `Found ${data.count} items`,
}),
error: (error) => ({
title: "Failed",
body: error.message || "An error occurred",
}),
});
// data is the resolved value
console.log(data);const data = await toast.promise(fetch("/api/data"), {
loading: { title: "Loading..." },
success: { title: "Loaded" },
error: { title: "Failed" },
});Loading toasts don't auto-close by default.
const id = toast.loading("Uploading", "0%");
// Simulate progress
let progress = 0;
const interval = setInterval(() => {
progress += 25;
toast.update(id, {
body: `${progress}%`,
progress,
});
if (progress >= 100) {
clearInterval(interval);
toast.update(id, {
type: "success",
title: "Complete",
body: "Upload finished.",
autoClose: 3000,
});
}
}, 1000);Add action buttons to toasts.
toast.show({
title: "Item deleted",
body: "The item was moved to trash.",
action: {
label: "Undo",
onClick: () => restoreItem(),
},
});toast.show({
title: "Post published",
body: "Your post is now live.",
action: {
label: "View post",
href: "/posts/123",
},
});toast.show({
title: "Confirm action",
body: "Are you sure?",
action: {
label: "Yes",
onClick: () => confirmAction(),
closeOnAction: false, // Toast won't dismiss after clicking
},
});Limit concurrent toasts and queue overflow.
const manager = createThemtfy({
maxToasts: 3, // Max 3 visible toasts
maxQueue: 20, // Max 20 queued toasts
});
manager.show({ title: "Toast 1" });
manager.show({ title: "Toast 2" });
manager.show({ title: "Toast 3" });
manager.show({ title: "Toast 4" }); // Queued
manager.show({ title: "Toast 5" }); // Queued
// Dismiss the first toast - Toast 4 will be promoted
manager.dismiss(firstToastId);
// Clear the queue
manager.clearQueue();Prevent duplicate notifications using dedupeKey.
// First call - creates toast
const id1 = toast.show({
title: "Network error",
dedupeKey: "network-error",
});
// Second call - updates existing toast
toast.show({
title: "Network restored",
dedupeKey: "network-error",
});
// id1 === id2 (same toast updated)Render toasts in a specific container.
const manager = createThemtfy({
container: document.getElementById("my-notifications"),
});Or with a function:
const manager = createThemtfy({
container: () => document.getElementById("my-container"),
});:root {
--themtfy-bg: #ffffff;
--themtfy-color: #1a1a1a;
--themtfy-radius: 8px;
--themtfy-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
--themtfy-success: #36cb8c;
--themtfy-error: #ff4e64;
--themtfy-warning: #ffbc3c;
--themtfy-info: #4d7eff;
}toast.configure({ theme: "dark" });toast.configure({ theme: "system" }); // Follows prefers-color-schemeThemtfy includes accessibility features by default:
- Roles:
role="status"for normal toasts,role="alert"for errors - ARIA Live:
aria-live="polite"for normal,aria-live="assertive"for errors - Keyboard: Close button is focusable with Tab, dismisses on Enter/Space
- Reduced Motion: Respects
prefers-reduced-motion - Screen Readers: Decorative icons are
aria-hidden="true"
By default, toasts do not steal focus from other elements.
Themtfy is safe to import in SSR environments (Next.js, Nuxt, Astro, etc.):
// Safe - no DOM access at import time
import { toast } from "themtfy";
// DOM is only touched when show() is called
function MyComponent() {
const handleClick = () => {
toast.show({ title: "Hello" }); // DOM touched here
};
return <button onClick={handleClick}>Show Toast</button>;
}The v1 API still works for backward compatibility:
import Themtfy from "themtfy";
new Themtfy({
title: "Hello",
body: "World",
position: "top-right",
autoClose: 5000,
});Before:
import Themtfy from "themtfy";
new Themtfy({ title: "Hello", body: "World" });After:
import { toast } from "themtfy";
toast.show({ title: "Hello", body: "World" });
// Or even simpler:
toast.success("Hello", "World");Option Changes:
| v1 | v2 |
|---|---|
variation |
type |
distanceX / distanceY |
offset: { x, y } |
onClose callback |
toast.on("dismiss", ...) event |
Modern browsers with ES2020 support:
- Chrome 80+
- Firefox 75+
- Safari 13.1+
- Edge 80+
| Format | Size | Gzipped |
|---|---|---|
| ESM | 38.5 KB | 10.4 KB |
| CJS | 32.2 KB | 9.7 KB |
| CSS | 8.0 KB | 1.7 KB |
MIT