Skip to content

ComponentsFeedback

Toast

Brief, non-blocking notifications that stack in a corner of the screen, powered by sonner. Typed toasts with themed icons, descriptions, actions, promise and loading states, in-place updates, custom content and six positions.

sonnerSource

Installation

pnpm dlx shadcn@latest add @desyne/toast

The CLI installs dependencies and any other components this one uses.

Usage

Mount <Toaster /> once, near the root of your app:

tsx
// app/layout.tsx
import { Toaster } from "@/components/ui/toast";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Toaster />
      </body>
    </html>
  );
}

Then call toast() from anywhere — event handlers, effects, even outside React:

tsx
import { toast } from "@/components/ui/toast";

toast.success("Changes saved");

One Toaster per app

Toasts only appear where a <Toaster /> is mounted, and every mounted <Toaster /> without an id renders every toast, so two of them show duplicates. Mount it once in the root layout, not inside pages or dialogs.

When to use

  • Toast — short feedback about something that just happened ("Saved", "Invite sent", "Deploy failed"), or background work the user doesn't have to wait on.
  • Alert — a message that belongs to the page and must stay visible until the situation changes, such as a form error or a billing problem.
  • Dialog — when the user must respond before continuing.
  • Don't put the only way to do something in a toast; it disappears. Actions like "Undo" should be shortcuts for something also reachable elsewhere.

Anatomy

tsx
<Toaster />                      {/* <section aria-live="polite"> › <ol> per position */}

toast(title, {
  icon,                          // leading icon (typed toasts get one by default)
  description,                   // secondary line
  action,                        // primary button
  cancel,                        // secondary button
});
PartRendersNotes
Toaster<section> › <ol>The live region and one list per position in use. Themed with popover tokens.
Toast<li data-sonner-toast>One notification. Carries data-type (success, error, …).
Icon<div data-icon>Lucide icon per type; tinted with the matching status color.
Title<div data-title>The first argument to toast().
Description<div data-description>Muted secondary text.
Action / Cancel<button>From action and cancel. The action closes the toast unless its onClick calls event.preventDefault(); cancel always closes it.
Close button<button aria-label="Close toast">Shown when closeButton is set on the toast or the Toaster. Never shown on loading or custom toasts.

Examples

Types

toast() shows a plain message. toast.success, toast.info, toast.warning and toast.error add a tinted icon that matches the status colors of the rest of the library.

Description

description adds a muted second line for detail that doesn't fit the title. Keep the title short enough to scan.

Actions

action renders a primary button, cancel a secondary one. Both take a label and an onClick, and close the toast after running. Use them for quick follow-ups like "Undo" or "View".

Promise

toast.promise shows a loading toast, then replaces it with the result. success and error can be functions that receive the resolved value or the rejection reason.

Updating a toast

Every call returns an id. Pass it back as id to update the same toast in place — for example, to walk a toast.loading toast through several steps before it succeeds.

Dismissing

toast.dismiss(id) closes one toast; toast.dismiss() closes them all. Use it for toasts that track a state the user can end elsewhere.

Duration and close button

Toasts close after 4 seconds by default. Shorten duration for trivial confirmations, lengthen it for anything with text to read, and use Infinity with closeButton for ongoing states. Timers pause while the user hovers the stack or the tab is hidden.

Custom icon

icon replaces the default icon, and adds one to plain toasts. Size it with size-4 to match the built-in icons.

Rich colors

richColors: true tints the whole toast with sonner's success, info, warning and error palettes. Set it on the <Toaster /> to apply it to every toast. The palettes switch to their dark variants automatically when your app is in dark mode (see Color mode); toggle the docs theme to compare.

Position

The Toaster's position (default bottom-right) sets where toasts appear. Individual toasts can override it with their own position.

Custom content

toast.custom renders your own JSX and keeps sonner's stacking, timing and swipe-to-dismiss. The render function receives the toast's id so buttons inside can dismiss it.

Recipes

Save with toast.promise

Submit a form, keep the button pending until the promise settles, and let toast.promise report success or the server's error message. finally resets the button either way.

Try “admin” to see the error state.

Delete with undo

Remove the item from the list right away and offer "Undo" in the toast. Commit the delete in onAutoClose, once the undo window has passed.

  • Launch plan.md

    Edited 2 hours ago

  • Pricing research.md

    Edited Yesterday

  • Interview notes.md

    Edited May 28

  • Retro — sprint 42.md

    Edited May 21

Export progress

A custom toast with a ProgressBar, re-rendered through the same id as the export runs, then swapped for a success toast with a download action.

Accessibility

  • The Toaster renders a <section> with aria-live="polite", so new toasts are announced without interrupting and without moving focus.
  • The region is labelled "Notifications" plus the hotkey. Press Alt+T to expand the stack and move focus into it; each toast is focusable.
  • Timers pause while the stack is expanded (hovered, or opened with the hotkey), while a toast is being pressed or swiped, and while the page is hidden.
  • sonner disables its own transitions and animations under prefers-reduced-motion, and the Lucide loading icon stops spinning (motion-reduce:animate-none).
  • Toasts disappear. Keep messages short, give important ones a longer duration, and never make a toast the only way to reach an action.
  • For errors that block the user, prefer an Alert near the cause, or pair the toast with one.

Keyboard

KeyAction
Alt+TExpands the toasts and moves focus to them (configurable with hotkey)
Tab / Shift+TabMoves between toasts and their buttons
Enter / SpaceActivates the focused action, cancel or close button
EscCollapses the expanded stack when focus is inside it

Styling

Theme

Toaster maps sonner's colors to your tokens: --normal-bg is --popover, --normal-text is --popover-foreground, --normal-border is --border and --border-radius is --radius. It also sets toastOptions.classNames for the toast shadow, muted descriptions, primary action buttons, muted cancel buttons and per-type icon colors, and replaces the icons with Lucide ones.

Your props are merged with these defaults instead of replacing them:

PropHow it merges
classNameJoined with the built-in toaster group classes (tailwind-merge).
styleShallow-merged over the token variables, so you can override one, such as --width, and keep the rest.
iconsMerged per type. Passing only success keeps the Lucide info, warning, error and loading icons.
toastOptionsEvery option is passed through; toastOptions.classNames is merged key by key with cn, so your toast or actionButton classes are added to the defaults.
tsx
<Toaster
  icons={{ success: <PartyPopperIcon className="size-4" /> }}
  toastOptions={{ classNames: { title: "!font-semibold" } }}
  style={{ "--width": "420px" } as React.CSSProperties}
/>

sonner's own styles aren't in a cascade layer, so the defaults use the ! (important) modifier. Use ! on your classes too when they need to beat a sonner style or a default, for example !bg-card.

Color mode

sonner's richColors and invert palettes depend on its theme. By default Toaster follows the .dark class on <html>, the same switch the theme tokens use, and watches it with a MutationObserver, so toasts change palette the moment your theme toggle runs. This works with next-themes (attribute="class"), fumadocs and hand-rolled toggles alike, and adds no dependency. During server rendering and hydration it uses "light", then switches after hydration.

Pass theme to take over: "light" or "dark" to pin it, or "system" to follow prefers-color-scheme instead of the class (useful if your app themes with media queries rather than .dark).

Custom toasts

toast.custom content still receives the toast classes (border, radius and shadow) on its <li>, but no background, padding or width. Give your root element bg-popover, padding and w-(--width) (356px by default).

Per-toast classes

Every toast accepts className, classNames (with keys such as title, description, actionButton, icon) and style, merged with the Toaster's defaults.

API Reference

Toaster

Prop

Type

toast()

tsx
toast(message, options?)          // plain
toast.success(message, options?)  // .info, .warning, .error, .loading, .message
toast.promise(promise, { loading, success, error, description?, finally? })
toast.custom((id) => <JSX />, options?)
toast.dismiss(id?)                // one toast, or all of them

Every call except dismiss returns the toast's id. toast.promise returns an object with unwrap(), which resolves or rejects with the original promise.

Prop

Type

toast.promise

Prop

Type

Also accepts every toast() option except description, which takes the form above. See the sonner docs for the full reference.

  • Alert — persistent, in-page messages.
  • Dialog — when the user must respond.
  • Progress Bar — progress inside a custom toast.
  • Button — isPending for the action that triggers the toast.