Ask Sonner

作者 emilkowalskie8a175de22ae無授權條款44K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫6 天前更新

Guide to Sonner, the React toast library — install and wire up the Toaster, pick the right toast() call, promise and loading toasts, updating, dismissing and persisting toasts, styling, theming and icons, positioning and multiple toasters. Use when working with Sonner or troubleshooting it — toasts that don't appear, appear twice, lose their styles, ignore Tailwind classes, sit behind a modal, or don't follow dark mode.

AI 產生的概覽

Sonner React 提示訊息庫指南:安裝設定、toast 呼叫、樣式、主題與疑難排解。

功能
這個技能是 Sonner(React 提示訊息函式庫)的參考指南。它說明如何掛載單一 Toaster,如何在 success、error、loading、promise 等 toast() 呼叫之間選擇,以及如何更新、關閉與保留提示、設定樣式與主題、圖示、定位和多個 Toaster。它也提供疑難排解表,把提示不顯示、重複出現或樣式遺失等症狀對應到原因與修正方式。
適用情境
當工作涉及 Sonner 時使用,無論是接線、渲染提示、設定樣式或修正問題。它也適合診斷提示不出現、出現兩次、樣式遺失、忽略 Tailwind 類別、被模態視窗遮住或未跟隨深色模式等情況。
執行需求
沒有指令碼,僅為說明文件。它引用隨附的 API.md 檔案以取得準確的屬性名稱、型別與預設值。內容針對使用 Sonner 套件的 React 專案,但閱讀本身不需要憑證或網路存取。

Working With Sonner

Initial Response

When this skill is first invoked without a specific question, respond only with:

I'm ready to help you set up, style, and troubleshoot Sonner, my knowledge comes from its author, Emil Kowalski.

Do not provide any other information until the user asks a question.

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 [blocked]; read it when you need an exact prop name, type, or default.

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 layoutimport { toast } from 'sonner';   // anywhere client-side

Picking the right call

You wantCall
Plain messagetoast('Title') — add { description } for a second line
Success / error / info / warning icontoast.success('…'), toast.error('…'), etc.
Spinner while you manage state yourselftoast.loading('…'), then update it by id
Loading → success/error tied to a promisetoast.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 shelltoast(<jsx />)
Custom JSX, no styles at alltoast.custom((t) => <jsx />) — headless, t gives you the id to dismiss

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.

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.

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 ignoredtheme 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 closesduration: Infinity, dismissible: false, or a toast.promise whose promise never settles — the loading toast waits forever.
toast.promise stuck on loadingIt 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 mobileoffset (desktop, default 32px) and mobileOffset (<600px, default 16px) — numbers, CSS strings, or per-side objects.

來源與署名

來源:emilkowalski/skills位於skills/ask-sonner提交e8a175d

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架