Skip to main content
PolyUI/docs

Feedback

Sonner Toast

A lightweight toast notification system supporting success, error, warning, info, and promise types with automatic light/dark theme adaptation.

Installation

bash
pnpm dlx shadcn@latest add sonner -c packages/react

Setup

Add the Toaster component to your root layout, typically at the end of body.

tsx
// app/layout.tsx
import { Toaster } from "@polyui/react/sonner"

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Toaster />
      </body>
    </html>
  )
}

Basic Usage

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"

export function ToastExample() {
  return (
    <div className="flex gap-2">
      <Button onClick={() => toast("This is a notification")}>Default</Button>
      <Button onClick={() => toast.success("Operation successful!")}>Success</Button>
      <Button onClick={() => toast.error("Operation failed!")}>Error</Button>
      <Button onClick={() => toast.warning("Please note!")}>Warning</Button>
      <Button onClick={() => toast.info("System info")}>Info</Button>
    </div>
  )
}

Promise Toast

Use toast.promise() to automatically handle loading, success, and error states.

tsx
toast.promise(
  fetch("/api/data").then((r) => r.json()),
  {
    loading: "Loading...",
    success: (data) => `Loaded successfully: ${data.name}`,
    error: "Load failed, please retry",
  }
)

Basic

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerBasic() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button variant="outline" onClick={() => toast("Action completed")}>
        Default
      </Button>
      <Button variant="outline" onClick={() => toast.success("Action completed")}>
        Success
      </Button>
      <Button variant="outline" onClick={() => toast.error("Error processing request")}>
        Error
      </Button>
      <Button variant="outline" onClick={() => toast.warning("Warning: check your input")}>
        Warning
      </Button>
      <Button variant="outline" onClick={() => toast.info("Info: note this detail")}>
        Info
      </Button>
    </div>
  )
}

With Description

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerWithDescription() {
  return (
    <Button variant="outline" onClick={() => toast("Event Created", { description: "Monday, January 3rd at 6:00pm" })}>
      With Description
    </Button>
  )
}

With Icon

tsx
import { toast } from "sonner"
import { TruckIcon } from "lucide-react"
import { Button } from "@polyui/react/button"
export function SonnerWithIcon() {
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast(
          <div className="flex items-center gap-2">
            <TruckIcon className="size-5 shrink-0" />
            Your order has been placed
          </div>
        )
      }
    >
      With Icon
    </Button>
  )
}

Closable

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerClosable() {
  return (
    <Button variant="outline" onClick={() => toast("Action completed", { closeButton: true })}>
      Closable
    </Button>
  )
}

With Action

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerWithAction() {
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast("Action completed", {
          action: { label: "Undo", onClick: () => console.log("Undo") },
        })
      }
    >
      With Action
    </Button>
  )
}

Promise

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerPromise() {
  const promise = () =>
    new Promise((resolve, reject) => setTimeout(() => (Math.random() < 0.5 ? resolve("foo") : reject("fox")), 2000))
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast.promise(promise, {
          loading: "Loading...",
          success: "Promise resolved!",
          error: "Promise rejected.",
        })
      }
    >
      Promise
    </Button>
  )
}

Positions

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerPositions() {
  return (
    <div className="grid grid-cols-2 gap-2">
      {(["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"] as const).map((pos) => (
        <Button
          key={pos}
          variant="outline"
          onClick={() =>
            toast(
              pos.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase()),
              { position: pos }
            )
          }
        >
          {pos.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase())}
        </Button>
      ))}
    </div>
  )
}

Soft

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerSoft() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button
        variant="outline"
        onClick={() =>
          toast.info("Info: note this detail", {
            style: {
              "--normal-bg":
                "color-mix(in oklab, light-dark(var(--color-sky-600), var(--color-sky-400)) 10%, var(--background))",
              "--normal-text": "light-dark(var(--color-sky-600), var(--color-sky-400))",
              "--normal-border": "light-dark(var(--color-sky-600), var(--color-sky-400))",
            } as React.CSSProperties,
          })
        }
      >
        Soft Info
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.success("Action completed", {
            style: {
              "--normal-bg":
                "color-mix(in oklab, light-dark(var(--color-green-600), var(--color-green-400)) 10%, var(--background))",
              "--normal-text": "light-dark(var(--color-green-600), var(--color-green-400))",
              "--normal-border": "light-dark(var(--color-green-600), var(--color-green-400))",
            } as React.CSSProperties,
          })
        }
      >
        Soft Success
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.warning("Warning: check your input", {
            style: {
              "--normal-bg":
                "color-mix(in oklab, light-dark(var(--color-amber-600), var(--color-amber-400)) 10%, var(--background))",
              "--normal-text": "light-dark(var(--color-amber-600), var(--color-amber-400))",
              "--normal-border": "light-dark(var(--color-amber-600), var(--color-amber-400))",
            } as React.CSSProperties,
          })
        }
      >
        Soft Warning
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.error("Error processing request", {
            style: {
              "--normal-bg": "color-mix(in oklab, var(--destructive) 10%, var(--background))",
              "--normal-text": "var(--destructive)",
              "--normal-border": "var(--destructive)",
            } as React.CSSProperties,
          })
        }
      >
        Soft Destructive
      </Button>
    </div>
  )
}

Outline

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerOutline() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button
        variant="outline"
        onClick={() =>
          toast.info("Info: note this detail", {
            style: {
              "--normal-bg": "var(--background)",
              "--normal-text": "light-dark(var(--color-sky-600), var(--color-sky-400))",
              "--normal-border": "light-dark(var(--color-sky-600), var(--color-sky-400))",
            } as React.CSSProperties,
          })
        }
      >
        Outline Info
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.success("Action completed", {
            style: {
              "--normal-bg": "var(--background)",
              "--normal-text": "light-dark(var(--color-green-600), var(--color-green-400))",
              "--normal-border": "light-dark(var(--color-green-600), var(--color-green-400))",
            } as React.CSSProperties,
          })
        }
      >
        Outline Success
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.warning("Warning: check your input", {
            style: {
              "--normal-bg": "var(--background)",
              "--normal-text": "light-dark(var(--color-amber-600), var(--color-amber-400))",
              "--normal-border": "light-dark(var(--color-amber-600), var(--color-amber-400))",
            } as React.CSSProperties,
          })
        }
      >
        Outline Warning
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.error("Error processing request", {
            style: {
              "--normal-bg": "var(--background)",
              "--normal-text": "var(--destructive)",
              "--normal-border": "var(--destructive)",
            } as React.CSSProperties,
          })
        }
      >
        Outline Destructive
      </Button>
    </div>
  )
}

Solid

tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function SonnerSolid() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button
        variant="outline"
        onClick={() =>
          toast.info("Info: note this detail", {
            style: {
              "--normal-bg": "light-dark(var(--color-sky-600), var(--color-sky-400))",
              "--normal-text": "var(--color-white)",
              "--normal-border": "light-dark(var(--color-sky-600), var(--color-sky-400))",
            } as React.CSSProperties,
          })
        }
      >
        Solid Info
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.success("Action completed", {
            style: {
              "--normal-bg": "light-dark(var(--color-green-600), var(--color-green-400))",
              "--normal-text": "var(--color-white)",
              "--normal-border": "light-dark(var(--color-green-600), var(--color-green-400))",
            } as React.CSSProperties,
          })
        }
      >
        Solid Success
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.warning("Warning: check your input", {
            style: {
              "--normal-bg": "light-dark(var(--color-amber-600), var(--color-amber-400))",
              "--normal-text": "var(--color-white)",
              "--normal-border": "light-dark(var(--color-amber-600), var(--color-amber-400))",
            } as React.CSSProperties,
          })
        }
      >
        Solid Warning
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.error("Error processing request", {
            style: {
              "--normal-bg":
                "light-dark(var(--destructive), color-mix(in oklab, var(--destructive) 60%, var(--background)))",
              "--normal-text": "var(--color-white)",
              "--normal-border": "transparent",
            } as React.CSSProperties,
          })
        }
      >
        Solid Destructive
      </Button>
    </div>
  )
}

Props

Toaster

属性类型默认值说明
position"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right""bottom-right"Position where toasts appear.
durationnumber4000Auto-dismiss duration in milliseconds.
richColorsbooleanfalseEnable richer color themes.
expandbooleanfalseExpand all toasts by default instead of stacking.
closeButtonbooleanfalseShow a close button on each toast.