ask-sonner

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Working 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
<Toaster />
and
toast()
live in API.md; read it when you need an exact prop name, type, or default.
这是针对toast库Sonner的使用指南。当你需要处理Sonner相关任务——配置组件、渲染toast、定制样式或排查问题时,请优先参考本文档。
<Toaster />
toast()
的完整属性表可查看API.md;当你需要确切的属性名称、类型或默认值时,请查阅该文档。

Setup

配置步骤

Two pieces, and only two:
  1. One
    <Toaster />
    , mounted once
    , as close to the root as possible (in Next.js:
    layout.tsx
    — it works inside server components). Never render it per-page or conditionally; a second mounted Toaster duplicates every toast.
  2. toast()
    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.
jsx
import { Toaster } from 'sonner'; // once, in layout
import { toast } from 'sonner';   // anywhere client-side
只需两步:
  1. 仅挂载一个
    <Toaster />
    组件
    ,尽可能靠近根节点(在Next.js中:
    layout.tsx
    ——它可在服务端组件中正常工作)。切勿在每页或条件渲染中重复挂载;第二个Toaster会导致每个toast重复显示。
  2. 在客户端代码中调用
    toast()
    ——比如事件处理器、副作用函数、回调函数中。它是一个普通函数,无需钩子或提供者,但在服务端无法生效:若在服务端动作中使用,需返回结果并在接收结果的客户端代码中调用
    toast()
jsx
import { Toaster } from 'sonner'; // 仅在layout中导入一次
import { toast } from 'sonner';   // 可在任意客户端代码中导入

Picking the right call

选择合适的调用方式

You wantCall
Plain message
toast('Title')
— add
{ description }
for a second line
Success / error / info / warning icon
toast.success('…')
,
toast.error('…')
, etc.
Spinner while you manage state yourself
toast.loading('…')
, then update it by id
Loading → success/error tied to a promise
toast.promise(promise, { loading, success, error })
— success/error accept functions receiving the resolved value/error
Button that does something
{ action: { label, onClick } }
— closes the toast unless
onClick
calls
event.preventDefault()
;
cancel
is the secondary variant
Custom JSX, default toast shell
toast(<jsx />)
Custom JSX, no styles at all
toast.custom((t) => <jsx />)
— headless,
t
gives you the id to dismiss
需求调用方式
普通消息
toast('Title')
——添加
{ description }
参数可显示第二行文本
带成功/错误/信息/警告图标
toast.success('…')
toast.error('…')
自定义状态管理时显示加载 spinner
toast.loading('…')
,随后通过id更新状态
加载→成功/错误状态与Promise绑定
toast.promise(promise, { loading, success, error })
——success/error参数可接收处理resolved值/error的函数
带交互按钮
{ action: { label, onClick } }
——除非
onClick
调用
event.preventDefault()
,否则点击按钮会关闭toast;
cancel
是次要按钮变体
自定义JSX,保留默认toast外壳
toast(<jsx />)
自定义JSX,完全不使用默认样式
toast.custom((t) => <jsx />)
——无样式模式,
t
提供用于关闭toast的id

Recipes

实用技巧

Update a toast — call
toast()
again with the same
id
; only the props you pass change. Switching to
toast.success(…, { id })
changes the type. This is how loading → success flows work without
toast.promise
:
jsx
const id = toast.loading('Uploading…');
toast.success('Uploaded', { id });
Persist
{ duration: Infinity }
. Dismiss
toast.dismiss(id)
, or
toast.dismiss()
for all. Read active toasts
useSonner()
in React,
toast.getActiveToasts()
outside it.
Links or components in the text — pass a function for the title or description:
toast(() => <a href="…">View</a>)
.
Multiple toasters — give each an
id
and target with
toast('…', { toasterId: 'canvas' })
. Without
toasterId
, every toaster renders the toast.
Close callbacks
onDismiss
fires on close button or swipe;
onAutoClose
fires on timeout. They are separate; there is no single "closed" callback.
更新toast——使用相同的
id
再次调用
toast()
;仅你传入的属性会被修改。切换为
toast.success(…, { id })
可改变toast类型。这是不使用
toast.promise
实现加载→成功流程的方式:
jsx
const id = toast.loading('上传中…');
toast.success('上传完成', { id });
持久化toast——设置
{ duration: Infinity }
关闭toast——调用
toast.dismiss(id)
关闭单个toast,或
toast.dismiss()
关闭所有toast。读取活跃toast——在React中使用
useSonner()
,在外部使用
toast.getActiveToasts()
文本中添加链接或组件——为title或description传入函数:
toast(() => <a href="…">查看</a>)
多Toaster管理——为每个Toaster设置
id
,并通过
toast('…', { toasterId: 'canvas' })
指定目标Toaster。若未设置
toasterId
,所有Toaster都会渲染该toast。
关闭回调——
onDismiss
在点击关闭按钮或滑动关闭时触发;
onAutoClose
在超时关闭时触发。二者是独立的,没有统一的“关闭”回调。

Styling — 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.
  1. Defaults — plus
    richColors
    on the Toaster for colorful success/error,
    invert
    to flip against the theme.
  2. Inline tweaks
    toastOptions={{ style: {…} }}
    on the Toaster for all toasts, or
    style
    per
    toast()
    call.
  3. Classes on parts
    toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}
    . Sonner's injected styles win the cascade, so every class needs
    !important
    (Tailwind:
    !text-red-900
    ). If you're marking more than a few things important, stop — go headless.
  4. Headless
    toast.custom()
    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()
    abstraction. (
    unstyled: true
    exists as a halfway house, but headless gives more control for the same effort.)
Icons — swap defaults per-type with the Toaster's
icons
prop, per-toast with
icon
, remove with
null
.
Theme
theme
defaults to
'light'
and does not track the OS. Pass
theme="system"
, or wire your theme provider:
<Toaster theme={resolvedTheme} />
from
next-themes
.
仅根据需求选择合适的层级;直接使用最高层级是可行的(这是推荐的最终方案),但停留在中间层级并不理想。
  1. 默认样式——可在Toaster上添加
    richColors
    参数开启彩色成功/错误样式,添加
    invert
    参数适配主题反转。
  2. 内联调整——在Toaster上设置
    toastOptions={{ style: {…} }}
    为所有toast统一设置样式,或在单个
    toast()
    调用中设置
    style
    参数。
  3. 为各部分添加类名——
    toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}
    。Sonner的内置样式优先级更高,因此每个类名都需要添加
    !important
    (Tailwind中使用
    !text-red-900
    )。若你需要标记多个元素为
    !important
    ,建议停止使用这种方式——切换到无样式模式。
  4. 无样式模式——使用
    toast.custom()
    传入自定义JSX,保留Sonner的定位、堆叠和滑动关闭功能。这是为设计系统定制toast的推荐方式:将其封装在你自己的
    toast()
    抽象中。(
    unstyled: true
    是过渡方案,但无样式模式能以相同的成本提供更多控制权。)
图标设置——通过Toaster的
icons
参数替换各类型toast的默认图标,或在单个toast中设置
icon
参数,设置为
null
可移除图标。
主题设置——
theme
默认值为
'light'
,不会跟随系统主题。可设置
theme="system"
,或关联你的主题提供者:从
next-themes
获取
<Toaster theme={resolvedTheme} />

Troubleshooting

故障排查

SymptomCause → fix
Toast never appearsNo
<Toaster />
mounted, or it unmounted (conditional render, per-page placement). Mount one at the root. If calling from a server action:
toast()
is client-only — call it with the action's result on the client.
Same toast appears twiceTwo Toasters mounted (layout and page) — keep one. Or
toast()
fired in an effect under React StrictMode's dev double-invoke — fire from the event handler instead, or pass a stable
id
so the second call updates rather than duplicates.
Tailwind/CSS classes have no effectDefault styles override them. Mark them
!important
, or use
unstyled
/ headless (see the ladder above).
Toasts render completely unstyled (common in Astro, view transitions)Sonner's injected stylesheet was lost — import it explicitly in a layout:
import 'sonner/dist/styles.css'
.
Unstyled inside Shadow DOMStyles land in
document.head
, not the shadow root. Copy the style tag whose text includes
[data-sonner-toaster]
into the shadow root.
Toast behind a modal/overlay, or clippedAn ancestor creates a stacking context (
transform
,
filter
,
overflow
) or the overlay out-z-indexes the toaster. Move
<Toaster />
to the document root, outside any dialog/portal container.
Dark mode ignored
theme
defaults to
'light'
— set
theme="system"
or pass the resolved theme (see Theme above).
Success/error look gray, not green/redThat's the default. Add
richColors
to the Toaster.
Toast never closes
duration: Infinity
,
dismissible: false
, or a
toast.promise
whose promise never settles — the loading toast waits forever.
toast.promise
stuck on loading
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 workDirections derive from
position
. Override with
swipeDirections
on the Toaster.
Toast shows up in every toasterMultiple toasters need targeting: give each Toaster an
id
and pass
toasterId
in the
toast()
call.
Toasts too close to the screen edge on mobile
offset
(desktop, default 32px) and
mobileOffset
(<600px, default 16px) — numbers, CSS strings, or per-side objects.
症状原因及解决方案
Toast从未显示未挂载
<Toaster />
组件,或组件已卸载(条件渲染、每页挂载)。在根节点挂载一个Toaster。若在服务端动作中调用:
toast()
仅在客户端生效——需在接收动作结果的客户端代码中调用它。
同一个toast重复显示两次挂载了两个Toaster(布局页面中都有)——仅保留一个。或者在React严格模式开发环境下,副作用函数中的
toast()
被调用两次——改为从事件处理器中触发,或传入稳定的
id
使第二次调用更新而非重复创建toast。
Tailwind/CSS类无效默认样式覆盖了自定义类。为自定义类添加
!important
,或使用
unstyled
/无样式模式(参考上述样式层级)。
Toast完全无样式(在Astro、视图过渡中常见)Sonner的注入样式表丢失——在布局中显式导入:
import 'sonner/dist/styles.css'
在Shadow DOM中无样式样式被注入到
document.head
,而非shadow root。将包含
[data-sonner-toaster]
的style标签复制到shadow root中。
Toast被模态框/遮罩层遮挡或被裁剪某个祖先元素创建了堆叠上下文(
transform
filter
overflow
),或遮罩层的z-index高于Toaster。将
<Toaster />
移到文档根节点,远离任何对话框/门户容器。
暗色模式未生效
theme
默认值为
'light'
——设置
theme="system"
或传入解析后的主题(参考上述主题设置)。
成功/错误toast显示灰色而非绿色/红色这是默认样式。在Toaster上添加
richColors
参数。
Toast始终无法关闭设置了
duration: Infinity
dismissible: false
,或
toast.promise
对应的Promise从未完成——加载状态的toast会一直等待。
toast.promise
一直停留在加载状态
第一个参数必须是Promise(或返回Promise的函数),且该Promise必须真正resolve/reject。
滑动关闭方向错误/无法工作方向由
position
参数决定。可在Toaster上通过
swipeDirections
参数覆盖默认设置。
Toast在所有Toaster中显示多Toaster需要指定目标:为每个Toaster设置
id
,并在
toast()
调用中传入
toasterId
参数。
在移动端Toast距离屏幕边缘过近使用
offset
(桌面端,默认32px)和
mobileOffset
(屏幕宽度<600px时,默认16px)——可传入数字、CSS字符串或分方向的对象。