反馈
Sonner Toast 通知
轻量级的 Toast 通知系统,支持 success、error、warning、info、promise 等类型,自动适配亮色/暗色主题。
安装
bash
pnpm dlx shadcn@latest add sonner -c packages/react布局配置
在根布局中添加 Toaster 组件,通常放在 body 末尾。
tsx
// app/layout.tsx
import { Toaster } from "@polyui/react/sonner"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster />
</body>
</html>
)
}基础用法
tsx
import { toast } from "sonner"
import { Button } from "@polyui/react/button"
export function ToastExample() {
return (
<div className="flex gap-2">
<Button onClick={() => toast("这是一条通知")}>默认</Button>
<Button onClick={() => toast.success("操作成功!")}>成功</Button>
<Button onClick={() => toast.error("操作失败!")}>错误</Button>
<Button onClick={() => toast.warning("请注意!")}>警告</Button>
<Button onClick={() => toast.info("系统信息")}>信息</Button>
</div>
)
}Promise Toast
使用 toast.promise() 自动处理加载、成功、失败三种状态。
tsx
toast.promise(
fetch("/api/data").then((r) => r.json()),
{
loading: "加载中...",
success: (data) => `加载成功:${data.name}`,
error: "加载失败,请重试",
}
)基础
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>
)
}带描述
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>
)
}带图标
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>
)
}可关闭
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>
)
}带操作
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>
)
}位置
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>
)
}柔和
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>
)
}描边
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>
)
}实色
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" | Toast 出现的位置。 |
duration | number | 4000 | Toast 自动消失的时间(毫秒)。 |
richColors | boolean | false | 启用更丰富的颜色主题。 |
expand | boolean | false | 默认展开所有 Toast(不堆叠)。 |
closeButton | boolean | false | 在每个 Toast 上显示关闭按钮。 |