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/reactSetup
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. |
duration | number | 4000 | Auto-dismiss duration in milliseconds. |
richColors | boolean | false | Enable richer color themes. |
expand | boolean | false | Expand all toasts by default instead of stacking. |
closeButton | boolean | false | Show a close button on each toast. |