react-toastify
React-Toastify is a React 18 and 19 notification system built around one mounted `ToastContainer` and an imperative `toast` API. It covers timed success and error messages, promise progress, manual updates, queues, stacked layouts, swipe dismissal, right-to-left layouts, custom React content, controlled progress, keyboard focus, and lifecycle events. Version 11.1 injects its default CSS when the styled container mounts; an unstyled entry is available for teams that want to own every visual rule.
A mature choice when a React app needs more than a one-line success message, especially promise updates, queues, custom content, or stacked notifications. Prefer a smaller primitive when the app has only a few local messages, and audit roles, timers, and global state before treating the defaults as finished UX.
Use it if
- You need a proven drop-in toast system with promise, update, queue, progress, and dismissal APIs
- Your React 18 or 19 app wants consistent notifications callable from event handlers and non-component modules
- You need stacked toasts, swipe-to-dismiss behavior, right-to-left support, themes, or multiple independently configured containers
- You want typed custom toast components that receive data, pause state, toast options, and a close function
- You are not on React 18 or 19: those are the only peer ranges declared by version 11.1.0
- A smaller notification primitive is enough: the default entry is 9.5 KB gzipped before React and React DOM, while a basic app may only need a local live-region component
- Your architecture avoids global imperative UI state: `toast()` calls feed mounted containers through a shared store, which can complicate isolated tests, server rendering, and ownership across application shells
- Your Content Security Policy cannot accept an injected style element and you will not pass a nonce or use `react-toastify/unstyled`: version 11.1 injects CSS when the styled container mounts
- Every notification should interrupt a screen reader: individual toasts default to role `alert`, while the container uses a polite live region, so high-volume informational events need deliberate roles and aria labels
Setup reality
Install `react-toastify` alongside React 18 or 19 and mount one `ToastContainer` near the client-side application root. In version 11.1 the normal entry automatically injects its stylesheet when that container mounts, so old tutorials that import `ReactToastify.css` are no longer required. A strict Content Security Policy needs the request nonce passed to `ToastContainer`; the 11.1 release added that prop specifically for the injected style tag. If the design system owns all styles, import from `react-toastify/unstyled` instead and provide the complete layout, transition, progress, and responsive CSS yourself. Next.js App Router users need a small `use client` wrapper because the container uses hooks and browser events. Calls made before a container mounts are queued, which is convenient in a single app but can hide a missing container in tests. Defaults are not neutral: toasts close after 5,000 milliseconds, pause on hover and window focus loss, do not close on content click, allow touch dragging, appear at the top right, use the light theme, and have role `alert`. The container also registers Alt+T to focus the first notification and Escape to collapse a stack; replace `hotKeys` and the aria label if those choices conflict with your app. A `limit` keeps excess toasts in a waiting queue, so dismissing visible toasts does not empty that queue unless you also call `toast.clearWaitingQueue()`. Mounting several containers requires stable `containerId` values on both the containers and toast calls. In tests, unmount containers and unsubscribe `toast.onChange` listeners between cases to prevent global state from leaking across assertions.
Patterns
Mount one styled toast containermount-toast-container
'use client';
import {ToastContainer, toast} from 'react-toastify';
export function Notifications() {
return <ToastContainer aria-label="Application notifications" />;
}
export function notifySaved() {
toast.success('Saved');
}The v11.1 styled entry injects CSS when the container mounts; Next.js App Router needs the client boundary shown here.
Show typed status variantsshow-toast-variants
toast.info('Sync started');
toast.success('Profile saved', {autoClose: 2500});
toast.warning('Storage is almost full');
toast.error('Upload failed', {autoClose: false});autoClose defaults to 5,000 milliseconds; false keeps a toast open until user or code dismisses it.
Turn a promise into pending, success, and error statestrack-promise
await toast.promise<User, Error>(saveUser(), {
pending: 'Saving user...',
success: {
render: ({data}) => `Saved ${data.name}`,
},
error: {
render: ({data}) => `Save failed: ${data.message}`,
},
});toast.promise returns the original promise, and resolved or rejected data is passed to the matching render callback.
Update a manually controlled loading toastupdate-loading-toast
const id = toast.loading('Uploading...');
try {
await uploadFile(file);
toast.update(id, {
render: 'Upload complete',
type: 'success',
isLoading: false,
autoClose: 3000,
});
} catch {
toast.update(id, {
render: 'Upload failed',
type: 'error',
isLoading: false,
autoClose: false,
});
}Reset isLoading and choose a new autoClose value or the updated toast can remain in its loading behavior.
Prevent duplicate notificationsprevent-duplicates
const OFFLINE_TOAST = 'offline';
if (!toast.isActive(OFFLINE_TOAST)) {
toast.error('You are offline', {
toastId: OFFLINE_TOAST,
autoClose: false,
});
}A stable toastId is usually enough to deduplicate; isActive is useful when creation depends on current visibility.
Dismiss visible toasts and clear a limited queuedismiss-and-clear
toast.dismiss();
toast.clearWaitingQueue();
// Or target one toast:
toast.dismiss('offline');dismiss removes displayed toasts, but when a container has a limit the waiting queue must be cleared separately.
Pass typed data to a custom toastrender-custom-content
import type {ToastContentProps} from 'react-toastify';
type Invite = {email: string};
function InviteToast({data, closeToast}: ToastContentProps<Invite>) {
return (
<div>
Invite sent to {data.email}
<button onClick={() => closeToast('undo')}>Undo</button>
</div>
);
}
toast<Invite>(InviteToast, {data: {email: 'ada@example.com'}});A string passed to closeToast becomes the onClose reason, which lets application code distinguish custom actions.
Configure stacked and limited notificationsconfigure-stacked-limit
<ToastContainer
position="bottom-right"
theme="dark"
stacked
newestOnTop
limit={3}
closeOnClick
nonce={cspNonce}
aria-label="Status messages"
/>The nonce is applied to the injected style tag; excess notifications wait in a queue once the limit is reached.
Route notifications to separate containersroute-to-container
<ToastContainer containerId="global" position="top-right" />
<ToastContainer containerId="editor" position="bottom-center" />
toast('Draft autosaved', {containerId: 'editor'});
toast.error('Session expired', {containerId: 'global'});Every targeted toast must use a containerId that matches a mounted container or it will not appear where expected.
Drive a toast progress bar manuallycontrol-progress
const id = toast('Uploading...', {
autoClose: false,
progress: 0,
});
upload.onProgress((loaded, total) => {
toast.update(id, {progress: loaded / total});
});
upload.onComplete(() => toast.done(id));Controlled progress values run from 0 to 1; toast.done advances to 1 and lets the notification complete.
Subscribe to global toast changesobserve-toast-lifecycle
const unsubscribe = toast.onChange((item) => {
console.log(item.id, item.status, item.reason);
});
// During application cleanup:
unsubscribe();Status is added, updated, or removed; v11.1 reports removal synchronously when toast.dismiss is called.
Own all styles with the unstyled builduse-unstyled-entry
import {ToastContainer, toast} from 'react-toastify/unstyled';
import './notifications.css';
<ToastContainer toastClassName="app-toast" />;
toast('Custom design system toast');The unstyled entry never injects the default stylesheet, so your CSS must handle placement, sizing, transitions, progress, and mobile layout.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sonner | npm | You want a more opinionated, compact toast component with a smaller configuration surface |
| react-hot-toast | npm | You want a headless-friendly React toaster with a concise API and custom render access |
| notistack | npm | Your application already uses Material UI patterns and wants snackbar queues plus provider-based configuration |
| @radix-ui/react-toast | npm | You want composable unstyled primitives and will build the notification store, styling, and application policy yourself |