mrkeyoor.com_
Sat 08 Aug 22:01 UTC
npmWeb Frontendupdated 08 Aug 2026

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.

Verdict

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.

API stability4/5The 2.x API has kept the same core split between toast calls, Toaster, ToastBar, and headless hooks, while adding options such as removeDelay and multiple toaster IDs without replacing the basic usage. The package exports explicit ESM, CommonJS, types, and headless paths. Its v2 migration did change styling and hook behavior, so another major could still affect custom renderers more than ordinary toast.success call sites.
Docs5/5The official site has separate references for toast, Toaster, ToastBar, useToaster, store access, styling, multiple toaster instances, and the v2 migration. It documents default durations, the infinite loading duration, the 1,000 ms removal delay, promise width jumps, accessibility options, and headless layout handlers with complete examples. The main omission is framework-specific guidance for client and server component boundaries.
Maintenance3/5Version 2.6.0 was published on 2025-08-15 and GitHub reports the last push on 2025-08-16, so there was no repository activity in the following year before this guide's date. The project is not archived and the release added meaningful API work, but GitHub reports 143 open issues and PRs, a sizable queue for UI infrastructure maintained primarily around one focused package.
Ecosystem5/5The npm downloads endpoint reports 3,707,142 downloads in its last-week window, and GitHub reports 10,971 stars. React 16 and newer are supported, full TypeScript declarations ship in the package, the standard and headless entry points cover web and React Native renderers, and examples across the React ecosystem commonly use the imperative toast API.

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
Skip it if

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

PackageRegistryPick it when
sonnernpmYou want a newer React toast API with polished stacking and a component style common in modern design systems
react-toastifynpmYou want a long-established React option with more built-in transitions, progress behavior, and configuration
notistacknpmYour application uses Material UI patterns and needs enqueueSnackbar plus provider-based queue control
react-toast-notificationsnpmYou maintain an older provider-based React codebase already built around its hook API