react-hot-toast
react-hot-toast is a React notification system with a module-level toast API and a Toaster component that renders queued messages. It covers plain, success, error, loading, custom JSX, and promise-driven notifications, with per-toast or global duration, position, icon, style, and accessibility options. Version 2.6 also supports independent toaster IDs and a headless entry point for custom web or React Native renderers. Its 4.8 KB gzipped package includes default presentation through goober, while the headless hooks expose lifecycle and layout state without that UI.
One of the easiest ways to add good-looking ephemeral feedback to a React app, especially when toast.promise and headless rendering matter. Skip it when notifications must persist, when runtime CSS-in-JS is off-limits, or when important errors need more than a disappearing polite announcement.
Use it if
- You want attractive React toasts with useful defaults and only one Toaster component to mount
- You need toast.promise to map pending, success, and error states onto one updating notification
- You want to trigger notifications outside component render code through a shared toast function
- You need a headless hook for custom rendering or React Native while keeping toast lifecycle management
- You are not using React: version 2.6 requires both react and react-dom peers at version 16 or newer for its standard web renderer
- Your design system forbids runtime CSS-in-JS: the default bundle depends on goober; use the headless export or a library whose styles match your stack
- You need a queued notification inbox, persistence, cross-tab sync, or server acknowledgement: this is an in-memory ephemeral toast store, not a notification center
- You need error messages to announce assertively by default: the source gives every new toast role status and aria-live polite unless you override ariaProps
- You want a quieter issue tracker before adopting UI infrastructure: GitHub reports 143 open issues and PRs, with the latest release and repository push both in August 2025
Setup reality
Install react-hot-toast and make sure the application already provides the react and react-dom peer dependencies at version 16 or newer. Mount exactly one <Toaster /> for the default toaster ID, normally near the application root, before expecting calls to appear. In a Next.js App Router project, put the Toaster in a client component because it uses React hooks and browser layout; importing the imperative toast function into event handlers is fine, but rendering Toaster in a server-only component is not. No provider, stylesheet import, config file, credentials, or native build is required. The standard entry includes goober for default styles; import react-hot-toast/headless if you will render everything yourself. The module-level store means any module can emit, which is convenient but makes test isolation and microfrontend ownership something you must design. Dismissed toasts stay mounted for an exit animation and are removed after 1,000 ms by default; toast.remove skips the animation immediately. Loading toasts have an infinite default duration, so every manual toast.loading call needs a success, error, dismiss, or remove path. toast.promise attaches handlers and returns the original promise, but its rendered success and error text should not expose raw server details. Different message lengths can make the toast width jump; the official docs recommend a minimum width for promise states. Global defaults belong on Toaster, while per-call options override them. Multiple Toasters require matching toasterId values on both renderer and toast calls. Default accessibility is role status with aria-live polite for every type, including errors, so urgent failures need explicit ariaProps and should still be represented inline when users must act on them.
Patterns
Mount the default toaster oncemount-toaster
import { Toaster } from 'react-hot-toast';
export function App() {
return (
<>
<Routes />
<Toaster position="top-right" />
</>
);
}Calls can come from anywhere, but a matching Toaster must be mounted to render them. Avoid accidentally mounting one per page.
Show success and error feedbackshow-basic-toasts
import toast from 'react-hot-toast';
toast.success('Profile saved');
toast.error('Could not save profile');Success defaults to 2,000 ms and error to 4,000 ms. Both use polite live-region semantics unless ariaProps are overridden.
Map a promise to loading, success, and errortrack-promise
await toast.promise(saveProfile(values), {
loading: 'Saving profile...',
success: (profile) => `Saved ${profile.name}`,
error: 'Save failed',
});toast.promise returns the original promise, so await still rejects when the operation fails. Handle the application error as well as showing the toast.
Keep promise states from changing widthstabilize-promise-width
toast.promise(upload(file), {
loading: 'Uploading...',
success: 'Upload complete',
error: 'Upload failed',
}, {
style: { minWidth: '220px' },
});The official docs recommend a minimum width because loading, success, and error messages often have different lengths.
Replace a manual loading toastupdate-loading-toast
const id = toast.loading('Publishing...');
try {
await publish();
toast.success('Published', { id });
} catch (error) {
toast.error('Publish failed', { id });
}Loading toasts default to infinite duration. Reuse the returned id on every completion path so a spinner is not left behind.
Prevent duplicate event toastsprevent-duplicates
toast.success('Copied to clipboard', {
id: 'clipboard-copy',
});A stable id upserts the existing toast. Use ids per logical event, or unrelated messages can overwrite one another.
Dismiss one toast with its exit animationdismiss-with-animation
const id = toast('Connection restored');
setTimeout(() => toast.dismiss(id), 1000);dismiss marks the toast hidden, then keeps it in the DOM for removeDelay, which defaults to 1,000 ms. toast.remove(id) skips that delay.
Render message content with a dismiss buttonadd-dismiss-button
toast((t) => (
<span>
Draft restored
<button onClick={() => toast.dismiss(t.id)}>Dismiss</button>
</span>
));Message functions receive the Toast object. Keep the button keyboard reachable and give icon-only controls an accessible name.
Set global and type-specific defaultsconfigure-global-defaults
<Toaster
toastOptions={{
duration: 5000,
style: { background: '#222', color: '#fff' },
success: { duration: 2500 },
error: {
ariaProps: { role: 'alert', 'aria-live': 'assertive' },
},
}}
/>Per-toast options override these defaults. Assertive announcements should be reserved for failures that genuinely need immediate attention.
Route a toast to a named arearoute-to-separate-toaster
<aside style={{ position: 'relative' }}>
<Toaster
toasterId="sidebar"
containerStyle={{ position: 'absolute' }}
/>
</aside>
toast('Sidebar updated', { toasterId: 'sidebar' });The toasterId must match on the call and renderer. Without it, the toast goes to the default instance.
Add a dismiss control to the default ToastBarcustomize-toast-bar
import { toast, Toaster, ToastBar } from 'react-hot-toast';
<Toaster>
{(t) => (
<ToastBar toast={t}>
{({ icon, message }) => (
<>
{icon}{message}
{t.type !== 'loading' && (
<button onClick={() => toast.dismiss(t.id)}>Close</button>
)}
</>
)}
</ToastBar>
)}
</Toaster>ToastBar preserves the built-in layout and icon behavior while letting the child renderer replace message content.
Build a minimal renderer from the headless entryrender-headless-toasts
import toast, { useToaster } from 'react-hot-toast/headless';
function Notifications() {
const { toasts, handlers } = useToaster();
return (
<div onMouseEnter={handlers.startPause} onMouseLeave={handlers.endPause}>
{toasts.filter((t) => t.visible).map((t) => (
<div key={t.id} {...t.ariaProps}>{t.message}</div>
))}
</div>
);
}
toast('Headless notification');This simple example handles visibility and pause behavior but not offsets, measured heights, or function-valued messages. Use resolveValue and calculateOffset for a full renderer.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sonner | npm | You want a newer React toast API with polished stacking and a component style common in modern design systems |
| react-toastify | npm | You want a long-established React option with more built-in transitions, progress behavior, and configuration |
| notistack | npm | Your application uses Material UI patterns and needs enqueueSnackbar plus provider-based queue control |
| react-toast-notifications | npm | You maintain an older provider-based React codebase already built around its hook API |