Dependencies
tailwindcssshadcn/uisonnerlucide-react
shadcn/ui components
npx shadcn@latest add sonnernpx shadcn@latest add button

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.

tsx
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.

tsx
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.

tsx
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.

tsx
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">&quot;Best Angular template I&apos;ve used.&quot;</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

Run npx shadcn@latest add sonner. This installs the sonner package and creates components/ui/sonner.tsx. Add <Toaster /> once in your root layout. Then use toast() from 'sonner' anywhere in your app.
Add <Toaster /> once in your root layout file (layout.tsx in Next.js App Router, or App.tsx): import { Toaster } from '@/components/ui/sonner'. Place it at the end of your body/root div. You only need one Toaster for the entire app.
Pass position prop to Toaster: <Toaster position='top-right' />. Options: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right. Default is bottom-right.
Server actions can't call toast() directly. Instead return a result from the server action, handle it in the client component, then call toast() based on the result: const result = await serverAction(). if (result.success) toast.success('Done'). else toast.error(result.error).

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.

Related Components