shadcn/ui uses Sonner for toast notifications — the best React toast library available. Add <Toaster /> once in your layout and call toast() from anywhere.
Installation
npx shadcn@latest add sonner
Setup — Add Toaster to Layout
// app/layout.tsx
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster position="bottom-right" richColors />
</body>
</html>
)
}
API Reference
import { toast } from "sonner"
toast("Message") // default
toast.success("Saved!") // green
toast.error("Failed!") // red
toast.warning("Warning!") // yellow
toast.info("Info") // blue
toast.loading("Loading...") // spinner
toast.promise(promise, { loading, success, error })
toast.custom((id) => <div>JSX</div>) // custom
toast.dismiss() // dismiss all
toast.dismiss(id) // dismiss one
1. Basic Sonner Toasts
Success, error, warning and info toasts using shadcn/ui Sonner. Add the Toaster in your layout once.
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
// Add <Toaster /> once in your root layout:
// import { Toaster } from "@/components/ui/sonner"
// <Toaster position="bottom-right" richColors />
export default function BasicToasts() {
return (
<>
<Toaster position="bottom-right" richColors />
<div className="flex flex-wrap gap-3">
<Button
onClick={() => toast.success("Template published successfully!")}
className="bg-green-600 hover:bg-green-700"
>
✅ Success Toast
</Button>
<Button
variant="destructive"
onClick={() => toast.error("Failed to save. Please try again.")}
>
❌ Error Toast
</Button>
<Button
variant="outline"
onClick={() => toast.warning("Your session expires in 5 minutes.")}
>
⚠️ Warning Toast
</Button>
<Button
variant="outline"
onClick={() => toast.info("New template update available.")}
>
ℹ️ Info Toast
</Button>
<Button
variant="outline"
onClick={() => toast("Notification", { description: "Here's a toast with a description below the title." })}
>
📋 With Description
</Button>
</div>
</>
)
}2. Promise Toast (Loading → Success/Error)
Toast that shows loading state while a promise resolves, then shows success or error.
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
function fakeApiCall(succeed: boolean): Promise<{ message: string }> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (succeed) resolve({ message: "Template uploaded successfully" })
else reject(new Error("Upload failed — server error"))
}, 2000)
})
}
export default function PromiseToasts() {
const handleUpload = (succeed: boolean) => {
toast.promise(fakeApiCall(succeed), {
loading: "Uploading template...",
success: (data) => data.message,
error: (err) => err.message,
})
}
const handleSave = () => {
toast.promise(
new Promise((resolve) => setTimeout(resolve, 1500)),
{
loading: "Saving changes...",
success: "All changes saved!",
error: "Failed to save changes.",
}
)
}
return (
<>
<Toaster position="bottom-right" richColors />
<div className="space-y-4">
<div>
<p className="text-sm text-muted-foreground mb-3">
Promise toasts show loading → success or error automatically
</p>
<div className="flex flex-wrap gap-3">
<Button
style={{ background: "#fd4766" }}
onClick={() => handleUpload(true)}
>
Upload (Success)
</Button>
<Button
variant="outline"
onClick={() => handleUpload(false)}
>
Upload (Fail)
</Button>
<Button
variant="outline"
onClick={handleSave}
>
Save Changes
</Button>
</div>
</div>
</div>
</>
)
}3. Toast with Action Button
Toasts with clickable action buttons — undo delete, view details, retry failed actions.
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export default function ActionToasts() {
const handleDelete = () => {
toast("Template deleted", {
description: "Marvel Angular Dashboard has been removed.",
action: {
label: "Undo",
onClick: () => toast.success("Delete undone — template restored"),
},
duration: 5000,
})
}
const handleSend = () => {
toast.success("Email sent!", {
description: "Your invoice has been sent to gagan@example.com",
action: {
label: "View",
onClick: () => console.log("View email"),
},
})
}
const handleError = () => {
toast.error("Connection failed", {
description: "Unable to reach the server.",
action: {
label: "Retry",
onClick: () => toast.loading("Retrying..."),
},
})
}
return (
<>
<Toaster position="bottom-right" richColors />
<div className="flex flex-wrap gap-3">
<Button variant="destructive" onClick={handleDelete}>
Delete Template (with Undo)
</Button>
<Button
style={{ background: "#fd4766" }}
onClick={handleSend}
>
Send Email (with View)
</Button>
<Button variant="outline" onClick={handleError}>
Trigger Error (with Retry)
</Button>
</div>
</>
)
}4. Custom Toast with JSX Content
Fully custom toast content using JSX — for rich notifications beyond standard patterns.
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { ShoppingCart, User, Bell, Star } from "lucide-react"
export default function CustomToasts() {
const showSaleToast = () => {
toast.custom((id) => (
<div
className="flex items-start gap-3 p-4 bg-white border rounded-xl shadow-lg w-80"
style={{ borderColor: "#fd4766" }}
>
<div
className="w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0"
style={{ background: "rgba(253,71,102,0.1)" }}
>
<ShoppingCart size={18} style={{ color: "#fd4766" }} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold">New Sale! 🎉</p>
<p className="text-xs text-gray-500 mt-0.5">Marvel Dashboard purchased by Rahul Kumar</p>
<p className="text-xs font-semibold mt-1" style={{ color: "#fd4766" }}>+$29.00</p>
</div>
<button
onClick={() => toast.dismiss(id)}
className="text-gray-300 hover:text-gray-500 text-lg leading-none"
>
×
</button>
</div>
))
}
const showReviewToast = () => {
toast.custom((id) => (
<div className="flex items-start gap-3 p-4 bg-white border border-yellow-200 rounded-xl shadow-lg w-80">
<div className="w-9 h-9 rounded-lg bg-yellow-50 flex items-center justify-center flex-shrink-0">
<Star size={18} className="text-yellow-500" />
</div>
<div className="flex-1">
<p className="text-sm font-bold">New 5-star review!</p>
<div className="flex gap-0.5 my-1">
{Array(5).fill(null).map((_, i) => (
<Star key={i} size={12} className="text-yellow-400 fill-yellow-400" />
))}
</div>
<p className="text-xs text-gray-500 italic">"Best Angular template I've used."</p>
</div>
<button onClick={() => toast.dismiss(id)} className="text-gray-300 hover:text-gray-500 text-lg">×</button>
</div>
))
}
const showUserToast = () => {
toast.custom((id) => (
<div className="flex items-center gap-3 p-3 bg-white border rounded-xl shadow-lg w-72">
<div className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm font-bold flex-shrink-0">
AK
</div>
<div className="flex-1">
<p className="text-sm font-semibold">Arjun Kumar joined</p>
<p className="text-xs text-gray-400">Just now</p>
</div>
<button onClick={() => toast.dismiss(id)} className="text-gray-300 hover:text-gray-500 text-lg">×</button>
</div>
))
}
return (
<>
<Toaster position="bottom-right" />
<div className="flex flex-wrap gap-3">
<Button style={{ background: "#fd4766" }} onClick={showSaleToast}>
💰 Sale Notification
</Button>
<Button variant="outline" onClick={showReviewToast}>
⭐ Review Alert
</Button>
<Button variant="outline" onClick={showUserToast}>
👤 User Joined
</Button>
</div>
</>
)
}Frequently Asked Questions
Need a Full React + Next.js Dashboard Template?
Get a complete React + Next.js dashboard with 50+ components — built by the same team behind BootstrapPlanet.
Browse Templates →Use code FIRST30 for 30% off.