Skip to main content
PolyUI/docs

反馈

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 出现的位置。
durationnumber4000Toast 自动消失的时间(毫秒)。
richColorsbooleanfalse启用更丰富的颜色主题。
expandbooleanfalse默认展开所有 Toast(不堆叠)。
closeButtonbooleanfalse在每个 Toast 上显示关闭按钮。