A TypeScript rewrite of toastr 2.x — zero dependencies, fully typed, promise-based API, dark mode, and CSS animation presets. ~4 KB gzipped (JS 2.07 KB + CSS 2.02 KB).
| Feature | toastr 2.x | toastr-next 3.x |
|---|---|---|
| Dependencies | jQuery required | Zero |
| TypeScript | No | First-class |
| Animations | jQuery animate + easing | CSS @keyframes |
| Dark mode | Manual CSS | prefers-color-scheme + data-theme |
| Accessibility | Partial | Full (ARIA, keyboard) |
| Return value | jQuery object | ToastInstance (with dismissed Promise) |
| React | Third-party | Built-in hook + provider |
| Bundle formats | UMD only | ESM, CJS, UMD, IIFE |
npm install toastr-nextyarn add toastr-nextpnpm add toastr-nextCSS is automatically injected into the page when you import the library — no separate stylesheet import required.
Every snippet below is verified against the public API exported from toastr-next and toastr-next/react. Method signatures, option names, and return types map 1:1 to the current src/.
Three complete, copy-pasteable scenarios. Pick the one that matches your stack — each one is end-to-end (install → entry file → working notification) with nothing else required.
The bundler-friendly path. No special bundler configuration needed: toastr-next ships ESM and CJS entries, and the stylesheet is injected at runtime so you do not need a CSS loader to import .css.
npm install toastr-next<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>toastr-next demo</title>
</head>
<body>
<button id="save">Save</button>
<script type="module" src="/src/main.ts"></script>
</body>
</html>// src/main.ts
import { toastr } from "toastr-next";
// CSS is auto-injected on first import — no `import "toastr-next/style"` needed.
toastr.options = {
positionClass: "toast-top-right",
progressBar: true,
closeButton: true,
timeOut: 4000,
};
document.getElementById("save")!.addEventListener("click", async () => {
const t = toastr.info("Saving…", "", { timeOut: 0 });
try {
await saveToServer();
t.clear();
await t.dismissed;
toastr.success("Saved!");
} catch {
t.clear();
toastr.error("Save failed");
}
});
async function saveToServer(): Promise<void> {
await new Promise((r) => setTimeout(r, 1200));
}Run with npx vite. The same src/main.ts works unchanged under Webpack, Rollup, Parcel, or esbuild — only the HTML entry / bundler config differs.
If you prefer to manage CSS yourself (SSR pipelines, CSS purging, etc.), import
toastr-next/styleonce and the runtime injector becomes a no-op since the styles are already present.
The IIFE bundle defines window.toastr directly. Drop a single <script> into any HTML page and you are done.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>toastr-next CDN demo</title>
</head>
<body>
<button id="ok">Show success</button>
<button id="err">Show error</button>
<!-- Loads the IIFE bundle and registers window.toastr. CSS is auto-injected. -->
<script src="https://cdn.jsdelivr.net/npm/toastr-next@3/dist/toastr-next.iife.js"></script>
<script>
toastr.options = {
positionClass: "toast-top-right",
progressBar: true,
closeButton: true,
};
document.getElementById("ok").addEventListener("click", () => {
toastr.success("Saved!", "Success");
});
document.getElementById("err").addEventListener("click", () => {
toastr.error("Something went wrong", "Error");
});
</script>
</body>
</html>toastr-next@3tracks the latest 3.x patch; usetoastr-next@3.0.7for an exact pin.- unpkg works too:
https://unpkg.com/toastr-next@3/dist/toastr-next.iife.js.
npm install toastr-next
# React 17+ is required as a peer dependency// src/App.tsx
import { ToastrProvider } from "toastr-next/react";
import { SaveButton } from "./SaveButton";
export default function App() {
return (
<>
<ToastrProvider
position="toast-top-right"
options={{ progressBar: true, closeButton: true, timeOut: 4000 }}
/>
<main style={{ padding: 24 }}>
<h1>My app</h1>
<SaveButton />
</main>
</>
);
}// src/SaveButton.tsx
import { useToastr } from "toastr-next/react";
export function SaveButton() {
const toast = useToastr();
async function handleSave() {
const t = toast.info("Saving…", "", { timeOut: 0 });
try {
await saveToServer();
t.clear();
await t.dismissed;
toast.success("Saved!");
} catch {
t.clear();
toast.error("Save failed");
}
}
return <button onClick={handleSave}>Save</button>;
}
async function saveToServer(): Promise<void> {
await new Promise((r) => setTimeout(r, 1200));
}// src/main.tsx (Vite entry)
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(<App />);The React adapter is a thin wrapper around vanilla toastr — no React context is used, so useToastr works with or without the Provider. Mount the Provider only when you want a single place to set global defaults.
Next.js / Remix / any SSR framework:
importis safe on the server (the CSS injector and module-level code are SSR-safe), but callingtoast.*()on the server will throw because the runtime callsdocument.createElement. Trigger toasts from client components /"use client"boundaries only.
import { toastr } from "toastr-next";
// CSS is auto-injected on first import — no separate stylesheet needed.
toastr.success("Saved!");
toastr.error("Something went wrong");
toastr.info("Update available");
toastr.warning("Low disk space");
// Optional title as the second argument
toastr.success("Profile updated", "Saved");Each method signature: (message: string, title?: string, options?: ToastrOptions) => ToastInstance.
Pass an options object as the third argument. Per-call options override global defaults. See the Options table for the full list.
toastr.success("Upload complete", "Done", {
progressBar: true,
closeButton: true,
animation: "bounce",
positionClass: "toast-bottom-right",
timeOut: 4000,
});Assigning to toastr.options replaces the defaults object (not merged). Set it once near your app entry, then override per call as needed.
toastr.options = {
positionClass: "toast-top-right",
progressBar: true,
closeButton: true,
animation: "fade",
timeOut: 5000,
};
// You can also mutate a single key:
toastr.options.timeOut = 3000;Set timeOut: 0. If closeOnHover is left at its default (true), also set extendedTimeOut: 0, otherwise the toast will dismiss extendedTimeOut ms after the pointer leaves.
toastr.warning("Action required", "Confirm to proceed", {
timeOut: 0,
extendedTimeOut: 0,
closeButton: true,
});Each toastr.*() call returns a ToastInstance synchronously. instance.dismissed resolves after the toast's hide animation finishes (or immediately on browsers without the Web Animations API).
const toast = toastr.info("Saving…", "", { timeOut: 0 });
await saveToServer();
toast.clear(); // trigger the dismiss animation
await toast.dismissed; // wait until the toast is fully gone
toastr.success("Saved!");const toast = toastr.info("Processing…");
toast.clear(); // dismiss with animation
toast.remove(); // remove from DOM immediately, no animationtoastr.clear(); // animate every visible toast out
toastr.remove(); // remove every toast immediately, no animationtoastr.subscribe(callback) returns an unsubscribe function. The callback fires for every state transition on every toast.
const unsubscribe = toastr.subscribe((event) => {
// event.state: "shown" | "hidden" | "clicked"
// event.type: "success" | "error" | "info" | "warning"
console.log(event.toastId, event.type, event.state, event.message);
});
// Later — stop listening
unsubscribe();When preventDuplicates is enabled, a second call with the same message while a copy is still on screen is suppressed. The returned ToastInstance is a safe no-op: dismissed resolves immediately and clear() / remove() are no-ops — so await and chaining still work without null checks.
toastr.options.preventDuplicates = true;
toastr.info("Loading…");
const suppressed = toastr.info("Loading…"); // not rendered — duplicate
await suppressed.dismissed; // resolves immediatelyBoth exports live under the toastr-next/react entry point. The React adapter is a thin wrapper around the vanilla toastr — no React context is used, so the hook works with or without the Provider.
<ToastrProvider /> is self-closing. It renders null and only configures the global toastr.options. Do not wrap children inside it.
// App.tsx
import { ToastrProvider } from "toastr-next/react";
export default function App() {
return (
<>
<ToastrProvider
position="toast-top-right"
options={{ progressBar: true, closeButton: true }}
/>
<Routes />
</>
);
}Provider props (all optional):
| Prop | Type | Purpose |
|---|---|---|
position |
ToastPosition |
Shorthand for options.positionClass (default toast-top-right) |
options |
ToastrOptions |
Full options object applied as global defaults |
onReady |
(t: typeof toastr) => void |
Fired once after defaults are applied |
// Home.tsx
import { useToastr } from "toastr-next/react";
export function Home() {
const toast = useToastr();
async function handleSave() {
try {
await save();
toast.success("Saved!", "Success");
} catch {
toast.error("Save failed", "Error");
}
}
return (
<div>
<button onClick={() => toast.info("Hello!")}>Info</button>
<button onClick={() => toast.warning("Heads up")}>Warning</button>
<button onClick={handleSave}>Save</button>
</div>
);
}The hook returns { success, info, warning, error, clear, remove }. Each method has the same signature as toastr.success(...) and returns a ToastInstance. clear and remove are the global versions — they dismiss every visible toast, not just ones triggered through this hook.
Defaults passed to useToastr are merged into every call made through it. Per-call options still take precedence.
const toast = useToastr({ closeButton: true, timeOut: 6000 });
toast.info("Inherits closeButton + timeOut from the hook");
toast.warning("Per-call override", "", { timeOut: 0 }); // stickyimport { useToastr } from "toastr-next/react";
export function UploadButton() {
const toast = useToastr();
async function handleUpload() {
const t = toast.info("Uploading…", "", { timeOut: 0, closeButton: true });
try {
await uploadFile();
t.clear();
await t.dismissed;
toast.success("Upload complete!");
} catch {
t.clear();
toast.error("Upload failed");
}
}
return <button onClick={handleUpload}>Upload</button>;
}useToastr does not expose subscribe. Use the global toastr.subscribe() inside an effect and return the unsubscribe function for cleanup.
import { useEffect } from "react";
import { toastr } from "toastr-next";
export function ToastLogger() {
useEffect(() => {
const unsubscribe = toastr.subscribe((e) => console.log(e));
return () => { unsubscribe(); };
}, []);
return null;
}All exported types are re-exported from the package entry:
import type {
ToastrOptions,
ToastInstance,
ToastEvent,
ToastType, // "success" | "error" | "info" | "warning"
ToastPosition, // 8 position-class string literals
AnimationType, // "fade" | "slide" | "bounce" | "flip"
ToastState, // "shown" | "hidden" | "clicked"
} from "toastr-next";All options can be set globally on toastr.options or passed per-call as the third argument.
toastr.options.timeOut = 3000;
toastr.success("Hello", "Title", { closeButton: true, animation: "slide" });| Option | Type | Default | Description |
|---|---|---|---|
allowHtml |
boolean |
false |
Render HTML in title and message (use with trusted content only) |
animation |
'fade' | 'slide' | 'bounce' | 'flip' |
'fade' |
CSS @keyframes animation preset |
closeButton |
boolean |
false |
Show a close button |
closeHtml |
string |
'<button>…</button>' |
Custom close button HTML |
closeOnHover |
boolean |
true |
Pause countdown while hovering |
extendedTimeOut |
number |
1000 |
Ms to keep toast open after hover |
newestOnTop |
boolean |
true |
Stack newest toasts on top |
onCloseClick |
(e: MouseEvent) => void |
— | Callback when close button clicked |
onHidden |
() => void |
— | Callback after toast is hidden |
onShown |
() => void |
— | Callback after toast is shown |
onclick |
(e: MouseEvent) => void |
— | Callback when toast body is clicked |
positionClass |
string |
'toast-top-right' |
Container position class |
preventDuplicates |
boolean |
false |
Suppress duplicate messages |
progressBar |
boolean |
false |
Show a progress bar |
rtl |
boolean |
false |
Right-to-left layout |
tapToDismiss |
boolean |
true |
Click toast body to dismiss |
target |
string |
'body' |
CSS selector for the container parent |
timeOut |
number |
5000 |
Ms before auto-dismiss (0 = sticky) |
Removed from 2.x:
showMethod,hideMethod,closeMethod,showEasing,hideEasing,closeEasing,escapeHtml. Useanimationfor animations andallowHtmlfor HTML content control.
Four built-in CSS @keyframes presets — no jQuery, no easing plugin required.
toastr.success("Saved!", "", { animation: "fade" }); // default
toastr.info("Note", "", { animation: "slide" });
toastr.warning("Heads up", "", { animation: "bounce" });
toastr.error("Failed", "", { animation: "flip" });Dark styles are applied automatically via prefers-color-scheme: dark. You can also force a theme by setting data-theme on any ancestor element (including <html>):
<!-- Force dark mode -->
<html data-theme="dark">
...
</html>
<!-- Force light mode (overrides OS preference) -->
<html data-theme="light">
...
</html>- The toast container is a live region (
role="log",aria-live="polite") - Each toast uses
role="alert"+aria-live="assertive"(errors/warnings) orrole="status"+aria-live="polite"(info/success), witharia-atomic="true" - Close buttons have a descriptive
aria-label - Toasts are focusable (
tabindex="0") and participate in natural tab order, so users can Tab into a toast and Tab again to reach its close button - Keyboard support: pressing
Escapewhile a toast is focused dismisses that toast
Every toastr.*() call returns a ToastInstance synchronously.
interface ToastInstance {
/** Resolves when the toast has finished its dismiss animation. */
dismissed: Promise<void>;
/** Programmatically remove the toast immediately (no animation). */
remove(): void;
/** Trigger the dismiss animation then remove. */
clear(): void;
}// Wait until the user dismisses the toast before proceeding
const toast = toastr.success("File uploaded");
await toast.dismissed;
navigateAway();
// Or dismiss programmatically
const toast = toastr.info("Processing…", "", { timeOut: 0 });
await doWork();
toast.clear();// Remove all toasts immediately (no animation)
toastr.remove();
// Dismiss all toasts with animation
toastr.clear();
// Subscribe to all toast lifecycle events; returns an unsubscribe function
const unsubscribe = toastr.subscribe((event) => {
console.log(event.state); // 'shown' | 'hidden' | 'clicked'
console.log(event.type); // 'success' | 'error' | 'info' | 'warning'
});
// Stop listening
unsubscribe();toastr.options.timeOut = 0;
toastr.options.extendedTimeOut = 0;
toastr.warning("This will not go away on its own.", "Action required");toast-top-right (default)
toast-top-left
toast-top-center
toast-top-full-width
toast-bottom-right
toast-bottom-left
toast-bottom-center
toast-bottom-full-width
No CSS import needed — styles are bundled into the JS. For CDN usage:
<script src="https://cdn.jsdelivr.net/npm/toastr-next@3/dist/toastr-next.iife.js"></script>
<script>
toastr.success("Hello from CDN!");
</script>If you prefer to manage CSS yourself (e.g. SSR or custom CSS pipelines), the stylesheet is still available separately:
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/toastr-next@3/dist/toastr-next.css"
/>| Format | Path | Use case |
|---|---|---|
| ESM | dist/toastr-next.es.js |
Bundlers (Vite, Webpack, Rollup) |
| CJS | dist/toastr-next.cjs.js |
Node.js / CommonJS |
| UMD | dist/toastr-next.umd.js |
Legacy bundlers |
| IIFE | dist/toastr-next.iife.js |
<script> tag, CDN |
- <script src="jquery.min.js"></script>
- <script src="toastr.min.js"></script>
+ <!-- toastr-next has no dependencies -->- npm install jquery toastr
+ npm install toastr-next- import toastr from 'toastr';
- import 'toastr/build/toastr.min.css';
+ import { toastr } from 'toastr-next';
+ // No CSS import needed — styles are auto-injected!- toastr.options.showMethod = 'slideDown';
- toastr.options.hideMethod = 'slideUp';
- toastr.options.showEasing = 'easeOutBounce';
- toastr.options.hideEasing = 'easeInBack';
+ toastr.options.animation = 'slide';The showMethod, hideMethod, closeMethod, showEasing, hideEasing, and closeEasing options are removed. Use the animation option with one of the four presets: 'fade' 'slide' 'bounce' 'flip'.
subscribe() now returns an unsubscribe function instead of void:
- toastr.subscribe(callback);
+ const unsubscribe = toastr.subscribe(callback);
+ // later…
+ unsubscribe();toastr.success() (and all variants) now return a ToastInstance synchronously instead of a jQuery object:
- const $toast = toastr.success('Saved!');
- $toast.css('color', 'red'); // jQuery DOM manipulation
+ const instance = toastr.success('Saved!');
+ await instance.dismissed; // wait for it to close// Block until the user closes the toast
const instance = toastr.warning("Are you sure?", "", {
timeOut: 0,
closeButton: true,
});
await instance.dismissed;
proceed();HTML is escaped by default (secure). To render HTML content, opt in explicitly:
- toastr.success('<b>Bold</b>', '', { escapeHtml: false });
+ toastr.success('<b>Bold</b>', '', { allowHtml: true });# Install dependencies
npm install
# Run tests (31 Vitest tests)
npm test
# Watch mode
npm run test:watch
# Build all bundles + type declarations
npm run buildPull requests are welcome. Please:
- Pass all existing tests (
npm test) - Add tests for new behaviour
- Target the
mainbranch
MIT — see LICENSE.
Originally created by John Papa, Tim Ferrell, and Hans Fjällemark.
Inspired by notifer.js.
toastr-next 3.x is a TypeScript rewrite with zero dependencies.