ask-sonner
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWorking With Sonner
Sonner 使用指南
A guide skill for Sonner, the toast library. When a task involves Sonner — wiring it up, rendering toasts, styling them, or fixing them — answer from this file first. Full prop tables for and live in API.md; read it when you need an exact prop name, type, or default.
<Toaster />toast()这是针对toast库Sonner的使用指南。当你需要处理Sonner相关任务——配置组件、渲染toast、定制样式或排查问题时,请优先参考本文档。和的完整属性表可查看API.md;当你需要确切的属性名称、类型或默认值时,请查阅该文档。
<Toaster />toast()Setup
配置步骤
Two pieces, and only two:
- One , mounted once, as close to the root as possible (in Next.js:
<Toaster />— it works inside server components). Never render it per-page or conditionally; a second mounted Toaster duplicates every toast.layout.tsx - called from client code — event handlers, effects, callbacks. It's a plain function, no hook or provider needed, but it does nothing on the server: in a server action, return the result and call
toast()in the client code that receives it.toast()
jsx
import { Toaster } from 'sonner'; // once, in layout
import { toast } from 'sonner'; // anywhere client-side只需两步:
- 仅挂载一个组件,尽可能靠近根节点(在Next.js中:
<Toaster />——它可在服务端组件中正常工作)。切勿在每页或条件渲染中重复挂载;第二个Toaster会导致每个toast重复显示。layout.tsx - 在客户端代码中调用——比如事件处理器、副作用函数、回调函数中。它是一个普通函数,无需钩子或提供者,但在服务端无法生效:若在服务端动作中使用,需返回结果并在接收结果的客户端代码中调用
toast()。toast()
jsx
import { Toaster } from 'sonner'; // 仅在layout中导入一次
import { toast } from 'sonner'; // 可在任意客户端代码中导入Picking the right call
选择合适的调用方式
| You want | Call |
|---|---|
| Plain message | |
| Success / error / info / warning icon | |
| Spinner while you manage state yourself | |
| Loading → success/error tied to a promise | |
| Button that does something | |
| Custom JSX, default toast shell | |
| Custom JSX, no styles at all | |
| 需求 | 调用方式 |
|---|---|
| 普通消息 | |
| 带成功/错误/信息/警告图标 | |
| 自定义状态管理时显示加载 spinner | |
| 加载→成功/错误状态与Promise绑定 | |
| 带交互按钮 | |
| 自定义JSX,保留默认toast外壳 | |
| 自定义JSX,完全不使用默认样式 | |
Recipes
实用技巧
Update a toast — call again with the same ; only the props you pass change. Switching to changes the type. This is how loading → success flows work without :
toast()idtoast.success(…, { id })toast.promisejsx
const id = toast.loading('Uploading…');
toast.success('Uploaded', { id });Persist — . Dismiss — , or for all. Read active toasts — in React, outside it.
{ duration: Infinity }toast.dismiss(id)toast.dismiss()useSonner()toast.getActiveToasts()Links or components in the text — pass a function for the title or description: .
toast(() => <a href="…">View</a>)Multiple toasters — give each an and target with . Without , every toaster renders the toast.
idtoast('…', { toasterId: 'canvas' })toasterIdClose callbacks — fires on close button or swipe; fires on timeout. They are separate; there is no single "closed" callback.
onDismissonAutoClose更新toast——使用相同的再次调用;仅你传入的属性会被修改。切换为可改变toast类型。这是不使用实现加载→成功流程的方式:
idtoast()toast.success(…, { id })toast.promisejsx
const id = toast.loading('上传中…');
toast.success('上传完成', { id });持久化toast——设置。关闭toast——调用关闭单个toast,或关闭所有toast。读取活跃toast——在React中使用,在外部使用。
{ duration: Infinity }toast.dismiss(id)toast.dismiss()useSonner()toast.getActiveToasts()文本中添加链接或组件——为title或description传入函数:。
toast(() => <a href="…">查看</a>)多Toaster管理——为每个Toaster设置,并通过指定目标Toaster。若未设置,所有Toaster都会渲染该toast。
idtoast('…', { toasterId: 'canvas' })toasterId关闭回调——在点击关闭按钮或滑动关闭时触发;在超时关闭时触发。二者是独立的,没有统一的“关闭”回调。
onDismissonAutoCloseStyling — the escalation ladder
样式定制——逐步进阶
Climb only as far as the change requires; jumping to the top rung too early is fine (it's the recommended end state), lingering in the middle is not.
- Defaults — plus on the Toaster for colorful success/error,
richColorsto flip against the theme.invert - Inline tweaks — on the Toaster for all toasts, or
toastOptions={{ style: {…} }}perstylecall.toast() - Classes on parts — . Sonner's injected styles win the cascade, so every class needs
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}(Tailwind:!important). If you're marking more than a few things important, stop — go headless.!text-red-900 - Headless — with your own JSX, keeping Sonner's positioning, stacking, and swipe. The recommended approach for a design-system toast: wrap it in your own
toast.custom()abstraction. (toast()exists as a halfway house, but headless gives more control for the same effort.)unstyled: true
Icons — swap defaults per-type with the Toaster's prop, per-toast with , remove with .
iconsiconnullTheme — defaults to and does not track the OS. Pass , or wire your theme provider: from .
theme'light'theme="system"<Toaster theme={resolvedTheme} />next-themes仅根据需求选择合适的层级;直接使用最高层级是可行的(这是推荐的最终方案),但停留在中间层级并不理想。
- 默认样式——可在Toaster上添加参数开启彩色成功/错误样式,添加
richColors参数适配主题反转。invert - 内联调整——在Toaster上设置为所有toast统一设置样式,或在单个
toastOptions={{ style: {…} }}调用中设置toast()参数。style - 为各部分添加类名——。Sonner的内置样式优先级更高,因此每个类名都需要添加
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}(Tailwind中使用!important)。若你需要标记多个元素为!text-red-900,建议停止使用这种方式——切换到无样式模式。!important - 无样式模式——使用传入自定义JSX,保留Sonner的定位、堆叠和滑动关闭功能。这是为设计系统定制toast的推荐方式:将其封装在你自己的
toast.custom()抽象中。(toast()是过渡方案,但无样式模式能以相同的成本提供更多控制权。)unstyled: true
图标设置——通过Toaster的参数替换各类型toast的默认图标,或在单个toast中设置参数,设置为可移除图标。
iconsiconnull主题设置——默认值为,不会跟随系统主题。可设置,或关联你的主题提供者:从获取。
theme'light'theme="system"next-themes<Toaster theme={resolvedTheme} />Troubleshooting
故障排查
| Symptom | Cause → fix |
|---|---|
| Toast never appears | No |
| Same toast appears twice | Two Toasters mounted (layout and page) — keep one. Or |
| Tailwind/CSS classes have no effect | Default styles override them. Mark them |
| Toasts render completely unstyled (common in Astro, view transitions) | Sonner's injected stylesheet was lost — import it explicitly in a layout: |
| Unstyled inside Shadow DOM | Styles land in |
| Toast behind a modal/overlay, or clipped | An ancestor creates a stacking context ( |
| Dark mode ignored | |
| Success/error look gray, not green/red | That's the default. Add |
| Toast never closes | |
| It needs a promise (or a function returning one) as its first argument, and the promise must actually resolve/reject. |
| Swipe-to-dismiss goes the wrong way / doesn't work | Directions derive from |
| Toast shows up in every toaster | Multiple toasters need targeting: give each Toaster an |
| Toasts too close to the screen edge on mobile | |
| 症状 | 原因及解决方案 |
|---|---|
| Toast从未显示 | 未挂载 |
| 同一个toast重复显示两次 | 挂载了两个Toaster(布局和页面中都有)——仅保留一个。或者在React严格模式开发环境下,副作用函数中的 |
| Tailwind/CSS类无效 | 默认样式覆盖了自定义类。为自定义类添加 |
| Toast完全无样式(在Astro、视图过渡中常见) | Sonner的注入样式表丢失——在布局中显式导入: |
| 在Shadow DOM中无样式 | 样式被注入到 |
| Toast被模态框/遮罩层遮挡或被裁剪 | 某个祖先元素创建了堆叠上下文( |
| 暗色模式未生效 | |
| 成功/错误toast显示灰色而非绿色/红色 | 这是默认样式。在Toaster上添加 |
| Toast始终无法关闭 | 设置了 |
| 第一个参数必须是Promise(或返回Promise的函数),且该Promise必须真正resolve/reject。 |
| 滑动关闭方向错误/无法工作 | 方向由 |
| Toast在所有Toaster中显示 | 多Toaster需要指定目标:为每个Toaster设置 |
| 在移动端Toast距离屏幕边缘过近 | 使用 |