Toast
Toast notifications with Sonner - success, error, loading, and custom toasts in shadcn/ui.
Search across all documentation pages
Toast notifications with Sonner - success, error, loading, and custom toasts in shadcn/ui.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Quick-reference recipe card - copy-paste ready.
npx shadcn@latest add sonner// app/layout.tsx - add the Toaster
import { Toaster } from "@/components/ui/sonner";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
);
}// Usage anywhere
import { toast } from "sonner";
toast("Default notification");
toast.success("Saved successfully!");
toast.error("Something went wrong");
toast.info("Did you know?");
toast.warning("Check your input");
toast.loading("Processing...");When to reach for this: When you need non-blocking feedback for user actions - saves, deletes, errors, and background task progress.
"use client";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
export function ToastShowcase() {
// Simple toasts
function showSuccess() {
toast.success("Profile updated", {
description: "Your changes have been saved.",
});
}
function showError() {
toast.error("Failed to save", {
description: "Please check your connection and try again.",
action: {
label: "Retry",
onClick: () => console.log("Retrying..."),
},
});
}
// Promise toast - shows loading, then success or error
function handleSave() {
const savePromise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.3 ? resolve("done") : reject(new Error("fail"));
}, 2000);
});
toast.promise(savePromise, {
loading: "Saving changes...",
success: "Changes saved!",
error: "Failed to save. Try again.",
});
}
// Custom toast with JSX
function showCustom() {
toast.custom((id) => (
<div className="flex items-center gap-3 rounded-lg border bg-white p-4 shadow-lg dark:bg-gray-950">
<div className="size-10 rounded-full bg-blue-100 flex items-center justify-center">
<span className="text-blue-600 text-lg">!</span>
</div>
<div className="flex-1">
<p className="text-sm font-medium">New message from Alice</p>
<p className="text-xs text-muted-foreground">Hey, check out this feature!</p>
</div>
<Button size="sm" variant="outline" onClick={() => toast.dismiss(id)}>
Dismiss
</Button>
</div>
));
}
// Undo toast
function handleDelete() {
let undone = false;
toast("Item deleted", {
description: "The item has been moved to trash.",
action: {
label: "Undo",
onClick: () => {
undone = true;
toast.success("Restored!");
},
},
onAutoClose: () => {
if (!undone) {
// Actually delete
console.log("Permanently deleted");
}
},
});
}
return (
<div className="flex flex-wrap gap-3">
<Button onClick={showSuccess}>Success</Button>
<Button variant="destructive" onClick={showError}>Error + Retry</Button>
<Button variant="outline" onClick={handleSave}>Promise Toast</Button>
<Button variant="secondary" onClick={showCustom}>Custom</Button>
<Button variant="ghost" onClick={handleDelete}>Delete with Undo</Button>
</div>
);
}What this demonstrates:
toast.promise for async operations with loading/success/error statestoast.custom for fully custom JSX toasts<Toaster> component<Toaster> renders a fixed-position container that manages toast stacking and animationstoast() is a function call (not a hook) - call it from anywhere, including server action callbacksToaster configuration:
<Toaster
position="top-right" // or "top-left", "bottom-left", "bottom-right", "top-center", "bottom-center"
richColors // enables colored backgrounds for success/error
closeButton // shows a close button on each toast
duration={5000} // default auto-close duration in ms
expand={false} // whether toasts expand to show all at once
toastOptions={{
className: "border-border",
descriptionClassName: "text-muted-foreground",
}}
/>Toast in server action response:
// Server action
"use server";
export async function updateProfile(formData: FormData) {
// ... update logic
return { success: true, message: "Profile updated" };
}
// Client component
async function handleSubmit(formData: FormData) {
const result = await updateProfile(formData);
if (result.success) {
toast.success(result.message);
} else {
toast.error(result.message);
}
}Persistent toast (no auto-dismiss):
const toastId = toast.loading("Uploading file...", {
duration: Infinity,
});
// Later, update it
toast.success("Upload complete!", { id: toastId });
// Or dismiss it
toast.dismiss(toastId);Themed toasts with richColors:
// With <Toaster richColors />:
toast.success("Saved!"); // green background
toast.error("Failed!"); // red background
toast.info("Note:"); // blue background
toast.warning("Caution:"); // yellow background// toast returns a string ID
const id: string | number = toast("Hello");
// Dismiss a specific toast
toast.dismiss(id);
// Dismiss all toasts
toast.dismiss();
// toast.promise is generic
const promise: Promise<User> = fetchUser();
toast.promise(promise, {
loading: "Loading user...",
success: (user) => `Welcome, ${user.name}!`, // user is typed as User
error: (err) => `Error: ${err.message}`,
});
// Custom toast render function
toast.custom((id: string | number) => (
<div>Custom content <button onClick={() => toast.dismiss(id)}>Close</button></div>
));Missing <Toaster /> - If you forget to add <Toaster /> to your layout, toast() calls do nothing. Fix: Add it to your root layout.
Toast called during render - Calling toast() inside a component body (not an event handler) triggers during render. Fix: Call toast() in useEffect, event handlers, or after server actions.
Too many toasts - Rapid actions can flood the screen. Fix: Use toast.dismiss() before showing a new toast, or use id to update an existing toast.
richColors and dark mode - Without richColors, success/error toasts look the same. With it, they get colored backgrounds that may need dark mode adjustments. Fix: Test both themes.
Sonner vs shadcn toast - shadcn previously had its own toast component. The current recommendation is Sonner. Fix: Use npx shadcn@latest add sonner, not the old toast component.
| Alternative | Use When | Don't Use When |
|---|---|---|
| react-hot-toast | You want a lightweight alternative with similar API | Sonner covers your needs |
| Native browser notifications | You need OS-level notifications (with permission) | In-app feedback is sufficient |
| Inline alerts | The feedback is tied to a specific section of the page | You need non-blocking, transient feedback |
| Snackbar (MUI) | You use Material UI | You use shadcn/ui |
<Toaster /> to your root layout (app/layout.tsx)toast() calls silently do nothingnpx shadcn@latest add sonnertoast() - default notificationtoast.success() - success feedbacktoast.error() - error feedbacktoast.info() - informationaltoast.warning() - warningtoast.loading() - loading spinnertoast.promise(savePromise, {
loading: "Saving changes...",
success: "Changes saved!",
error: "Failed to save. Try again.",
});It automatically transitions through loading, success, and error states based on the promise.
onAutoClose to perform the actual deletion only if undo was not clickedtoast() in event handlers, useEffect, or after server action responsesconst toastId = toast.loading("Uploading file...", {
duration: Infinity,
});
// Later, update or dismiss it:
toast.success("Upload complete!", { id: toastId });toast() or toast.loading(){ id: toastId } to a subsequent toast.success() or toast.error() calltoast.custom((id) => (
<div className="flex items-center gap-3 rounded-lg border p-4 shadow-lg">
<p className="text-sm font-medium">Custom message</p>
<Button size="sm" onClick={() => toast.dismiss(id)}>Dismiss</Button>
</div>
));npx shadcn@latest add sonner)toast component in new projectsconst promise: Promise<User> = fetchUser();
toast.promise(promise, {
loading: "Loading user...",
success: (user) => `Welcome, ${user.name}!`, // user is typed as User
error: (err) => `Error: ${err.message}`,
});top-right, top-left, top-centerbottom-right, bottom-left, bottom-center<Toaster position="top-right" />Reviewed by Chris St. John·Last updated Jul 16, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥