使用 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(),在 React 外部使用 toast.getActiveToasts()。
文字中的連結或元件 — 為標題或描述傳遞一個函數: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)。如果您對多個東西標記了 important,請停止 — 改用無頭模式。 - 無頭模式 — 使用
toast.custom()搭配您自己的 JSX,保留 Sonner 的定位、堆疊和滑動功能。這是設計系統吐司的推薦方法:將其包裝在您自己的toast()抽象層中。(unstyled: true可作為過渡方案,但無頭模式在相同努力下提供了更多控制。)
圖示 — 使用 Toaster 的 icons 屬性按類型切換預設圖示,使用 icon 按吐司切換,使用 null 移除。
主題 — theme 預設為 'light',且不會追蹤作業系統。傳遞 theme="system",或連接您的主題 Provider:使用 next-themes 的 <Toaster theme={resolvedTheme} />。
疑難排解
| 症狀 | 原因 → 修正 |
|---|---|
| 吐司從未出現 | 未掛載 <Toaster />,或它被卸載了 (有條件渲染、按頁面放置)。在根節點掛載一個。如果是從伺服器動作呼叫:toast() 僅限客戶端 — 在客戶端使用動作的結果來呼叫它。 |
| 同一個吐司出現兩次 | 掛載了兩個 Toaster (layout 和 page) — 只保留一個。或者 toast() 在 React StrictMode 的開發模式下於副作用中被雙重觸發 — 改為從事件處理器觸發,或傳遞一個穩定的 id,以便第二次呼叫更新而非複製。 |
| Tailwind/CSS 類別無效 | 預設樣式會覆蓋它們。將它們標記為 !important,或使用 unstyled / 無頭模式 (請參閱上述分級)。 |
| 吐司完全無樣式渲染 (常見於 Astro、視角轉場) | Sonner 注入的樣式表遺失了 — 在 layout 中明確匯入它:import 'sonner/dist/styles.css'。 |
| 在 Shadow DOM 中無樣式 | 樣式會進入 document.head,而非 shadow root。將文字包含 [data-sonner-toaster] 的樣式標籤複製到 shadow root 中。 |
| 吐司在 Modal/覆蓋層後面,或被裁剪 | 祖先元素建立了堆疊上下文 (transform、filter、overflow),或覆蓋層的 z-index 高於 Toaster。將 <Toaster /> 移至文件根節點,位於任何對話框/Portal 容器之外。 |
| 深色模式被忽略 | 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 字串或按邊界區分的物件。 |