前端开发
使用 Sonner 时如何正确挂载、调用、样式化与排查…
在 React 项目中接入、调用、定制和修复 Sonner toast 的完整指南。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
使用 Sonner 时如何正确挂载、调用特定
解决思路
- 首次被调用且用户没有具体问题时,只回复:I'm ready to help you set up, style, and troubleshoot Sonner, my knowledge comes from its author, Emil Kowalski. 在用户提问前不要提供其他信息。
- 需要精确的 prop 名、类型或默认值时,阅读 API.md 中的 <Toaster /> 和 toast() 完整 prop 表。
- 只做两件设置:在尽可能靠近根的位置只挂载一个 <Toaster />(Next.js 中放在 layout.tsx,可在 server components 内工作);永远不要按页面或条件渲染,第二个已挂载 Toaster 会复制每个 toast。
- 只在客户端代码(事件处理器、effects、callbacks)中调用 toast();它是普通函数,不需要 hook 或 provider,但在服务端什么都不做;在 server action 中返回结果,并在接收结果的客户端代码里调用 toast()。
- 导入方式:在 layout 中一次性 import { Toaster } from 'sonner';在任意客户端位置 import { toast } from 'sonner'。
- 普通消息:用 toast('Title'),加 { description } 显示第二行。
- 成功/错误/信息/警告图标:用 toast.success('…')、toast.error('…') 等。
- 自己管理状态时显示 spinner:用 toast.loading('…'),然后按 id 更新。
- 把 loading → success/error 绑定到 promise:用 toast.promise(promise, { loading, success, error });success/error 接受接收 resolved value/error 的函数。
- 需要按钮执行操作:用 { action: { label, onClick } };除非 onClick 调用 event.preventDefault(),否则会关闭 toast;cancel 是次要变体。
- 自定义 JSX 但保留默认 toast 外壳:用 toast(<jsx />)。
- 自定义 JSX 且完全不要样式:用 toast.custom((t) => <jsx />);这是 headless,t 提供 id 供 dismiss。
- 更新 toast:用相同 id 再次调用 toast();只有传入的 props 会改变。切换为 toast.success(…, { id }) 会改变类型。这也是不用 toast.promise 实现 loading → success 的方法:const id = toast.loading('Uploading…'); toast.success('Uploaded', { id });
- 持久显示:使用 { duration: Infinity }。
- 关闭:使用 toast.dismiss(id),或 toast.dismiss() 关闭全部。
- 读取活跃 toast:React 中用 useSonner(),React 外用 toast.getActiveToasts()。
- 在文本中放链接或组件:为 title 或 description 传入函数:toast(() => <a href="…">View</a>)。
- 多个 toaster:给每个 toaster 一个 id,并用 toast('…', { toasterId: 'canvas' }) 定向;不传 toasterId 时每个 toaster 都会渲染该 toast。
- 关闭回调:onDismiss 在关闭按钮或滑动时触发;onAutoClose 在超时时触发;二者分开,没有单一 closed 回调。
- 样式升级阶梯第 1 级:先用默认样式,并在 Toaster 上加 richColors 获得彩色 success/error,加 invert 反转主题。
- 样式升级阶梯第 2 级:在 Toaster 上用 toastOptions={{ style: {…} }} 为所有 toast 做内联调整,或在每次 toast() 调用中传 style。
- 样式升级阶梯第 3 级:用 toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }} 给各部分加类。Sonner 注入的样式会赢得级联,所以每个类都需要 !important(Tailwind:!text-red-900)。如果要标记多个 !important,停止,改用 headless。
- 样式升级阶梯第 4 级:用 toast.custom() 搭配自己的 JSX,保留 Sonner 的定位、堆叠和滑动。设计系统 toast 的推荐做法是用自己的 toast() 抽象包一层。(unstyled: true 是折中方案,但 headless 在相同成本下控制更多。)
- 图标:用 Toaster 的 icons prop 按类型替换默认图标,用每次 toast 的 icon 单独替换,用 null 移除。
- 主题:theme 默认 'light' 且不跟随 OS。传 theme="system",或接入主题 provider:从 next-themes 使用 <Toaster theme={resolvedTheme} />。
- 排查:toast 从不出现——没有挂载 <Toaster />,或它被卸载(条件渲染、按页面放置)。在根位置挂载一个。若从 server action 调用:toast() 仅客户端可用——在客户端用 action 的结果调用它。
- 排查:同一 toast 出现两次——挂载了两个 Toaster(layout 和 page)——只保留一个。或在 React StrictMode 开发双调用下于 effect 中触发 toast()——改为在事件处理器中触发,或传稳定 id 让第二次调用更新而不是重复。
- 排查:Tailwind/CSS 类无效——默认样式覆盖了它们。给它们加 !important,或使用 unstyled / headless(见升级阶梯)。
- 排查:toast 完全无样式(常见于 Astro、view transitions)——Sonner 注入的样式表丢失,在 layout 中显式导入:import 'sonner/dist/styles.css'。
- 排查:Shadow DOM 内无样式——样式落在 document.head,而不是 shadow root。把文本包含 [data-sonner-toaster] 的 style 标签复制到 shadow root。
- 排查:toast 在 modal/overlay 后面或被裁剪——某个祖先创建了 stacking context(transform、filter、overflow),或 overlay 的 z-index 高于 toaster。把 <Toaster /> 移到 document root,放在任何 dialog/portal 容器之外。
- 排查:暗色模式被忽略——theme 默认 'light'——设置 theme="system" 或传入解析后的主题。
- 排查:success/error 看起来是灰色而非绿色/红色——这是默认行为。给 Toaster 加 richColors。
- 排查:toast 从不关闭——duration: Infinity、dismissible: false,或 toast.promise 的 promise 永不 settle——loading toast 会永远等待。
- 排查:toast.promise 卡在 loading——它需要 promise(或返回 promise 的函数)作为第一个参数,且 promise 必须实际 resolve/reject。
- 排查:滑动关闭方向错误或不工作——方向来自 position。用 Toaster 上的 swipeDirections 覆盖。
- 排查:toast 出现在每个 toaster 中——多个 toaster 需要定向:给每个 Toaster 一个 id,并在 toast() 调用中传 toasterId。
- 排查:移动端 toast 离屏幕边缘太近——调整 offset(桌面,默认 32px)和 mobileOffset(<600px,默认 16px)——可以是数字、CSS 字符串或按边对象。
适用边界 · 注意事项
- 项目未使用 Sonner(或不是 React 客户端环境)时不适用。
- 无法在客户端调用 toast() 的纯服务端流程不能直接使用,需先返回结果到客户端。
- 已经采用其他 toast 方案且不打算迁移到 Sonner 的项目不适用。