Skip to content
Feedback

Hot Toast — Component Library Guide

Smoking hot React notifications. Lightweight, customizable, and accessible. Supports promise-based toasts, custom renders, and keyboard dismissal.

TypeScript MIT
2.2M weekly downloads
10.1k GitHub stars
~5kB bundle size
v2.4.1 latest version
toastnotificationspromiselightweightaccessible

TL;DR: React Hot Toast is a zero-dependency toast library with a hook-based API. Call toast('message') or toast.promise(fn, {loading, success, error}) from anywhere in your app. Renders via portal at the root. ~5kB gzipped. Official docs: react-hot-toast.com.

What is React Hot Toast?

React Hot Toast is a lightweight, zero-dependency toast notification library. It uses a hook-based API and renders toasts in a portal at the top level. The toast.promise() helper is particularly handy for async operations - it handles loading, success, and error states in one call.

The design choice that makes it pleasant is that toast() is callable from anywhere, not just inside a component. You import the function, call it in an event handler, a data-fetching utility, or a Redux thunk, and the message appears. There's no context to thread, no prop to pass down, no ref to wire up. You drop one <Toaster /> at the root and forget about it. Everything else is a plain function call.

That portability is why it spread so widely. Form submissions, API errors, copy-to-clipboard confirmations, and background sync results all need a quick "here's what happened" message, and they often fire from code that's nowhere near a component. A global toast function fits that reality better than a component you have to render in the right place.

Picking it over the alternatives

React Hot Toast is a solid, well-tested choice with a large community. It's slightly older than Sonner and has more manual customisation overhead for advanced styling, but it's battle-tested and widely deployed. If you want a library that's been hammered on in production for years and has answers to nearly every edge case on Stack Overflow, this is a safe pick.

How does it compare to Sonner? Both are excellent. Sonner has slicker stacking animations and newer design defaults, and it's the toast component shadcn/ui ships now. React Hot Toast has a larger install base, more community resources, and a headless mode that gives you total rendering control. My rule of thumb: reach for Sonner when you want gorgeous defaults with little effort, and React Hot Toast when you want a stable base you'll style heavily yourself. Neither is a wrong answer, and migrating between them later is a small job.

One thing to keep in mind regardless of choice: toasts are for transient, non-critical feedback. A failed payment deserves a persistent error in the form, not a message that vanishes in four seconds. Use toasts for confirmations and soft warnings, not for anything the user must act on.

Key Features

  • toast.promise() - wraps async operations with loading/success/error states
  • Custom renderers - pass JSX to toast.custom() for fully custom toast UI
  • Headless mode - use useToaster() hook to build your own toast UI
  • Pause on hover - toasts pause countdown on mouse enter
  • Accessible - ARIA live regions for screen readers

Installation

npm install react-hot-toast

Setup

import { Toaster } from 'react-hot-toast'

function App() {
  return (
    <>
      <YourApp />
      <Toaster
        position="top-right"
        toastOptions={{
          duration: 4000,
          style: { background: '#0d0e1a', color: '#f1f5f9' },
        }}
      />
    </>
  )
}

Basic Usage

import toast from 'react-hot-toast'

// Simple
toast('Message sent')
toast.success('Saved!')
toast.error('Failed to save')

// Promise
async function handleSubmit() {
  await toast.promise(
    submitForm(data),
    {
      loading: 'Submitting...',
      success: 'Form submitted!',
      error: (err) => `Error: ${err.message}`,
    }
  )
}

Custom Toast

toast.custom((t) => (
  <div className={`flex items-center gap-3 bg-white shadow-lg rounded-lg p-4 ${
    t.visible ? 'animate-enter' : 'animate-leave'
  }`}>
    <CheckCircle className="text-green-500" size={20} />
    <div>
      <p className="font-medium">Upload complete</p>
      <p className="text-sm text-slate-500">Your file has been uploaded</p>
    </div>
    <button onClick={() => toast.dismiss(t.id)} className="ml-auto text-slate-400">X</button>
  </div>
))

TypeScript Tips

Use the headless useToaster hook for full type control:

import { useToaster } from 'react-hot-toast'

function ToastContainer() {
  const { toasts, handlers } = useToaster()
  const { startPause, endPause, calculateOffset, updateHeight } = handlers

  return (
    <div onMouseEnter={startPause} onMouseLeave={endPause}>
      {toasts.map(t => {
        const offset = calculateOffset(t, { reverseOrder: false })
        const ref = (el: HTMLDivElement) => el && updateHeight(t.id, el.getBoundingClientRect().height)
        return (
          <div key={t.id} ref={ref} style={{ transform: `translateY(${offset}px)` }}>
            {/* render your toast */}
          </div>
        )
      })}
    </div>
  )
}

Accessibility and timing details

Toasts announce themselves through an ARIA live region, so screen reader users hear the message without losing their place. That's the part most hand-rolled notification systems skip, and it's the reason a library is worth using here at all. Keep your messages short and meaningful, because a live region reads the whole thing aloud and a wall of text is hard to follow by ear.

Timing deserves thought too. The default four-second duration is fine for a short confirmation, but it's too quick for a longer error message someone needs to read and digest. Bump the duration for error toasts, or set it to Infinity and let the user dismiss the message themselves. Pause-on-hover helps as well, since it gives anyone who's reading a moment to finish before the toast slides away.

Common Gotchas

  • Render <Toaster /> once - at the app root; duplicates cause conflicting animations and stacked, misplaced toasts.
  • toast.dismiss(id) - programmatically dismiss a specific toast; call without an ID to dismiss all of them at once.
  • Custom styles don't hot-reload - toastOptions.style set at <Toaster> level doesn't always pick up HMR changes; restart the dev server if styles seem stuck.
  • Deduplicate with an id - pass an id to toast() to update an existing toast instead of stacking a new one, which is handy for progress updates on the same action.
  • Sonner - the newer toast alternative with better stacking animations; the default in shadcn/ui
  • React 19 Guide - use toast notifications to surface results from React 19 Actions and useActionState

Frequently Asked Questions

Can I render multiple Toaster components?

No. You should render exactly one Toaster in your app root. Multiple Toaster instances conflict with each other and cause duplicate or incorrectly positioned toasts.

How do I style React Hot Toast to match my design system?

Pass a style object or className to the toastOptions prop on the Toaster component for global styles. For per-toast styles, pass style or className directly to the toast() call. For fully custom rendering, use toast.custom() with your own JSX.

How do I use React Hot Toast with a dark theme?

Pass a style object with background and color to toastOptions on the Toaster component. For example: toastOptions={{ style: { background: '#1e293b', color: '#f1f5f9' } }}. This applies dark colours to all toasts globally.

What does toast.promise() do?

toast.promise() takes a promise and shows a loading toast while it is pending, then automatically transitions to a success or error toast depending on whether the promise resolves or rejects. It returns the same promise so you can still await it.

How do I build a fully custom toast UI with React Hot Toast?

Use the useToaster hook to access all active toast objects and the handlers for pause-on-hover and height measurement. Render the toasts yourself with any styling or layout you choose, completely replacing the default Toaster component.