使用 Sonner
初始响应
当此技能首次被调用且没有具体问题时,仅回复:
我已准备好帮助您设置、样式化和排查 Sonner 问题,我的知识来源于其作者 Emil Kowalski。
在用户提出问题之前,不要提供任何其他信息。
这是一个面向 Sonner 吐司通知库的指南技能。当任务涉及 Sonner(包括配置、渲染、样式化或修复问题)时,请首先从此文件获取答案。<Toaster /> 和 toast() 的完整属性表位于 API.md 中;当您需要确切的属性名、类型或默认值时,请阅读该文件。
安装设置
只有两个步骤:
- 一个
<Toaster />,挂载一次,尽可能靠近根组件(在 Next.js 中:layout.tsx— 它可以运行在服务器组件内部)。切勿在每个页面或有条件地渲染它;第二个挂载的 Toaster 会重复显示每一个吐司。 - 从客户端代码调用
toast()— 事件处理函数、副作用、回调函数。它是一个普通函数,无需 Hook 或 Provider,但在服务器端无效果。在服务器操作中,应返回结果,并在接收结果的客户端代码中调用toast()。
import { Toaster } from 'sonner'; // 一次,在 layout 中
import { toast } from 'sonner'; // 在任何客户端组件中
选择合适的调用方式
| 目标 | 调用方式 |
|---|---|
| 纯文本消息 | toast('Title') — 添加 { description } 以显示第二行 |
| 成功/错误/信息/警告图标 | toast.success('…'), toast.error('…') 等 |
| 自己管理状态时显示旋转器 | toast.loading('…'),然后通过 ID 更新 |
| 加载 → 与 Promise 绑定的成功/错误 | toast.promise(promise, { loading, success, error }) — success/error 可接收返回已决值/错误的函数 |
| 带操作的按钮 | { action: { label, onClick } } — 除非 onClick 调用了 event.preventDefault(),否则会关闭吐司;cancel 是次要变体 |
| 自定义 JSX,使用默认吐司外壳 | toast(<jsx />) |
| 自定义 JSX,完全无样式 | toast.custom((t) => <jsx />) — 无头模式,t 提供用于关闭的 id |
常用技巧
更新吐司 — 使用相同的 id 再次调用 toast();仅改变您传递的属性。切换到 toast.success(…, { id }) 会改变类型。这是不使用 toast.promise 实现加载 → 成功流程的方法:
const id = toast.loading('上传中…');
toast.success('已上传', { id });
持久化 — { duration: Infinity }。关闭 — toast.dismiss(id),或 toast.dismiss() 关闭所有。读取活跃的吐司 — 在 React 中使用 useSonner(),在外部使用 toast.getActiveToasts()。
在文本中嵌入链接或组件 — 为 title 或 description 传递一个函数:toast(() => <a href="…">查看</a>)。
多个 Toaster — 为每个 Toaster 设置一个 id,并通过 toast('…', { toasterId: 'canvas' }) 定位。如果没有 toasterId,每个 Toaster 都会渲染该吐司。
关闭回调 — onDismiss 在点击关闭按钮或滑动时触发;onAutoClose 在超时时触发。它们是分开的;没有单一的“已关闭”回调。
样式 — 阶梯式升级方案
只升级到变更所需的层级;过早跳到最顶层是可以接受的(这是推荐的最终状态),但停留在中间层级则不然。
- 默认值 — 在 Toaster 上添加
richColors以实现彩色成功/错误提示,使用invert以反转主题。 - 内联调整 — 在 Toaster 上使用
toastOptions={{ style: {…} }}应用于所有吐司,或在单个toast()调用中使用style。 - 对部分应用类 — 使用
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}。Sonner 注入的样式会在级联中获胜,因此每个类都需要!important(Tailwind:!text-red-900)。如果您标记了多个重要项,请停止 — 转向无头模式。 - 无头模式 — 使用带自定义 JSX 的
toast.custom(),保留 Sonner 的定位、堆叠和滑动功能。这是设计系统吐司的推荐方法:将其封装在您自己的toast()抽象中。(unstyled: true作为一种中间方案存在,但无头模式在付出相同努力的情况下提供了更多控制权。)
图标 — 使用 Toaster 的 icons 属性按类型替换默认图标,使用 icon 按单个吐司替换,使用 null 移除图标。
主题 — theme 默认为 'light' 且不跟踪操作系统。请传入 theme="system",或连接您的主题提供程序:<Toaster theme={resolvedTheme} />(来自 next-themes)。
故障排查
| 症状 | 原因 → 修复 |
|---|---|
| 吐司从未显示 | 没有挂载 <Toaster />,或它被卸载了(条件渲染、每页放置)。在根组件挂载一个。如果从服务器操作调用:toast() 仅支持客户端 — 在客户端使用操作的结果调用它。 |
| 同一个吐司出现两次 | 挂载了两个 Toaster(layout 和 page)— 保留一个。或者在 React 严格模式的开发双重调用下,在副作用中触发了 toast() — 改为从事件处理函数触发,或传递一个稳定的 id,这样第二次调用会更新而不是重复。 |
| Tailwind/CSS 类无效 | 默认样式会覆盖它们。请标记为 !important,或使用 unstyled / 无头模式(参见上面的升级方案)。 |
| 吐司完全无样式渲染(常见于 Astro、视图过渡) | Sonner 注入的样式表丢失了 — 在布局中显式导入:import 'sonner/dist/styles.css'。 |
| 在 Shadow DOM 内无样式 | 样式被添加到 document.head,而不是 shadow root。请将包含 [data-sonner-toaster] 文本的 style 标签复制到 shadow root 中。 |
| 吐司位于模态框/覆盖层后面或被裁剪 | 某个祖先元素创建了层叠上下文(transform, filter, overflow)或覆盖层的 z-index 高于 toaster。将 <Toaster /> 移动到文档根组件,位于任何对话框/传送门容器之外。 |
| 深色模式被忽略 | theme 默认为 'light' — 请设置 theme="system" 或传递已解析的主题(参见上面的主题部分)。 |
| 成功/错误显示为灰色,而非绿色/红色 | 这是默认行为。请在 Toaster 上添加 richColors。 |
| 吐司从不关闭 | duration: Infinity、dismissible: false,或 toast.promise 的 Promise 从未完成 — 加载提示会一直等待。 |
toast.promise 卡在加载状态 | 它需要一个 Promise(或返回 Promise 的函数)作为第一个参数,并且该 Promise 必须实际 resolve/reject。 |
| 滑动关闭方向错误/不起作用 | 方向取决于 position。请在 Toaster 上使用 swipeDirections 覆盖。 |
| 吐司在每个 Toaster 中都显示 | 多个 Toaster 需要定位:为每个 Toaster 设置 id,并在 toast() 调用中传入 toasterId。 |
| 移动端上吐司离屏幕边缘太近 | 使用 offset(桌面端,默认 32px)和 mobileOffset(<600px,默认 16px)— 可以是数字、CSS 字符串或按边设置的对象。 |