AI 工具 20 min read

ask-sonner skill 實測:Sonner 作者的指南,有 1 個 API 不存在

ask-sonner 是 Sonner 作者 Emil Kowalski 寫的 skill,教 AI 設定與排查 React toast。它教的 getActiveToasts() 不在 Sonner 2.0.8 公開 API,5 組實測唯一編譯失敗的就是照抄那組,用時要叫 AI 跑 tsc。

ask-sonner skill 實測:Sonner 作者的指南,有 1 個 API 不存在
本文目錄 · 8
懶人包
  • ask-sonner 是 Sonner 作者 Emil Kowalski 寫的 AI agent skill,收在 emilkowalski/skills(MIT),2026-08-10 加入。內容是兩個檔:88 行的 SKILL.md(設定、選用哪種 toast、樣式、疑難排解)和 64 行的 API.md(props 表)。Sonner 自己的 README 也推薦用它來裝。
  • Sonner 本身很紅:npm 最新版 2.0.8,2026-09-19 到 10-18 這 30 天下載 2.14 億次。skill repo 在 2026-10-21 是 48,159 stars,這是 15 支 skill 共用的數字。
  • 我拿它對照 Sonner 2.0.8 的型別檔,找到一個會讓程式壞掉的錯:skill 教「在 React 外讀取 toast 用 toast.getActiveToasts()」,但 2.0.8 公開的是 toast.getToasts()。另外 offset 預設值寫 32px,原始碼是 24px。
  • 我用同一個需求讓 Claude Code 跑 5 組:唯一 npm run build 失敗的,就是載入了 skill、照抄 getActiveToasts 的那一組。點名 /ask-sonner 那組有自己去查型別、改用正確 API,也照 skill 用了更精簡的 toast.promise 寫法。
  • 沒點名的情況跑 2 次只觸發 1 次。建議點名使用,並且讓 AI 寫完一定跑一次 tsc。
  1. ask-sonner 是什麼?
  2. Sonner 是什麼、有多紅?
  3. 什麼時候會被觸發?
  4. API.md 跟 Sonner 2.0.8 對得上嗎?
  5. 實測:有 skill 跟沒 skill,寫出來差在哪?
  6. 怎麼安裝、怎麼點名?
  7. 適合誰?限制與坑
  8. 延伸資源

🍞 ask-sonner 是什麼?

ask-sonner 是一份給 AI coding agent 讀的 Sonner 使用手冊。Sonner 是 React 的 toast(右下角跳出來的通知)函式庫,作者跟這支 skill 是同一個人:Emil Kowalski。

它跟同 repo 其他 skill 不一樣。其他支多半在講動效原則或設計判斷,ask-sonner 只做一件事:讓 AI 在碰到 Sonner 時,先照作者本人的說法回答,不要靠訓練資料裡的舊印象亂寫。SKILL.md 裡寫的是「When a task involves Sonner — wiring it up, rendering toasts, styling them, or fixing them — answer from this file first」。

內容分成兩個檔:

檔案行數內容
SKILL.md88設定兩原則、該用哪種 toast() 的對照表、常用寫法(以 id 更新、持久、關閉、多個 toaster、關閉回呼)、樣式的四階升級、主題、13 列疑難排解表
API.md64<Toaster /> props、toast() options、函式清單,含型別與預設值
SKILL.md 裡我覺得最有用的是兩段。第一段是樣式的「升級梯子」:先用預設加 richColors,不夠再用 style,再不夠才用 classNames(而且每個 class 都要 !important,因為 Sonner 自帶的樣式會贏),標了太多 !important 就直接改用 toast.custom() 自己畫。第二段是疑難排解表,把「toast 不出現」「出現兩次」「Tailwind class 沒效果」「被 modal 蓋住」「深色模式沒跟上」這些症狀直接對到原因。

Sonner 官方 README 在安裝段落寫著:「Alternatively you can use the /ask-sonner skill to do it for you」,所以它算是作者推薦的入口。

📈 Sonner 是什麼、有多紅?

我在 2026-10-21 查了 npm registry 和 GitHub API:

項目數字
npm 最新版2.0.8(2026-08-09 發布,上一版 2.0.7 是 2025-08-02)
npm 下載量214,265,841 次(2026-09-19~10-18,npm last-month API)
emilkowalski/sonner repo13,059 stars、468 forks
emilkowalski/skills repo48,159 stars、2,785 forks,最後 push 2026-10-11
emilkowalski/skills 裡一共有 15 支 skill,GitHub 只能對整個 repo 加星,所以 48k 是整套共用的數字,不是 ask-sonner 一支的。

ask-sonner 加入 repo 的 commit 是 2026-08-10,跟 Sonner 2.0.8 發布只差一天。

🎯 什麼時候會被觸發?

SKILL.md 的 frontmatter 只有 name 和 description,沒有 disable-model-invocation,所以 Claude Code 可以自己判斷要不要載入它。description 寫的觸發情境很具體:裝 Toaster、選 toast() 呼叫、promise/loading toast、更新與關閉、樣式、主題、圖示、多個 toaster,以及「toast 不出現、出現兩次、樣式不見、Tailwind 沒效、被 modal 蓋住、不跟深色模式」這幾種問題。

另一個要知道的設定是「Initial Response」:如果你只打 /ask-sonner、後面沒帶問題,它被要求只回一句:

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

然後等你提問。所以點名時要把需求接在後面一起送,不要只打指令。

🔍 API.md 跟 Sonner 2.0.8 對得上嗎?

不完全對得上。我把 API.md 逐列跟 npm 上 sonner@2.0.8 的 dist/index.d.ts 和 dist/index.mjs 對照,大部分預設值一致(visibleToasts 3、gap 14、duration 4000、position bottom-right、theme light、mobileOffset 16px),但有幾處不一樣:

項目skill 寫的2.0.8 實際影響
在 React 外讀取 toasttoast.getActiveToasts()公開 API 只有 toast.getToasts() 和 toast.getHistory();getActiveToasts 是內部 ToastState 的方法,getToasts 裡面呼叫它照寫會 TypeScript 編譯失敗,執行時 TypeError
offset 預設'32px'原始碼 const VIEWPORT_OFFSET = '24px'只影響你對間距的預期
containerAriaLabel列在 toast() options是 <Toaster /> 的 prop,預設 'Notifications'放錯地方會被型別擋下
hotkey 型別stringstring[],預設 ['altKey', 'KeyT']照字串寫會型別錯誤
toastOptions「任何 toast() option」型別只開放一部分(className、style、duration、classNames、closeButton、unstyled、toasterId 等)例如 action 不能放在這裡設成全域預設
API.md 沒列、但 2.0.8 有的:<Toaster /> 的 duration、className、style、customAriaLabel;toast() 的 richColors、className、descriptionClassName;classNames 除了 skill 列的六個部位,還能指定 icon、content、loader 和各 type(success、error…);toast.promise 的設定還有 description、finally,回傳值帶 unwrap()。

getActiveToasts 這個錯不是 skill 自己發明的。我查了 Sonner 官方文件站的 Other 頁面,也是寫 toast.getActiveToasts();GitHub main 分支的 src/state.ts 匯出的一樣是 getHistory 和 getToasts。看起來是官方文件與程式碼不同步,skill 照著文件寫,於是一起錯。

🧪 實測:有 skill 跟沒 skill,寫出來差在哪?

我用 Vite + React + TypeScript 建了一個基底專案,裝 sonner@2.0.8,複製成 5 份,用同一句需求跑 claude -p --dangerously-skip-permissions(Claude Code 2.1.306,模型 Sonnet)。需求是:

1. 一顆「上傳」按鈕,呼叫 1.5 秒後隨機成功或失敗的假上傳函式(成功時回傳檔名),
   用 toast 顯示「上傳中」→「成功/失敗」,成功訊息要帶出回傳的檔名。
2. 成功的 toast 要有「復原」按鈕,點了之後再跳一個「已復原」的 toast。
3. toast 要跟著作業系統的深色模式切換,成功和失敗要有顏色。
4. 在 src/uploadApi.ts(不是 React 元件的一般模組)提供 countVisibleToasts(),
   回傳目前畫面上有幾個 toast;App 裡加一顆按鈕把這個數字 alert 出來。
不要安裝其他套件。寫完檔案就結束,不用解釋。

第 4 點是我刻意設計的:它對應 skill 裡「在 React 外讀取 toast」那一條,正好是 API.md 跟 2.0.8 對不上的地方。第 3 點對應 skill 的 Theme 段落(預設 light、不跟 OS)。

5 組的設定與結果:

組別設定skill 有沒有用上countVisibleToasts 寫法npm run build
auto1裝 skill、不點名沒有呼叫 Skilltoast.getToasts().length✅ 通過
auto2裝 skill、不點名有呼叫 Skilltoast.getActiveToasts().length❌ TS2339
named1需求前加 /ask-sonner有(內容直接注入)toast.getToasts() 再濾掉已關閉的✅ 通過
without1沒裝 skill—toast.getToasts().length✅ 通過
without2沒裝 skill—toast.getToasts() 再濾掉已關閉的✅ 通過
建置指令是範本預設的 tsc -b && vite build。auto2 是唯一失敗的一組: auto2 的建置輸出節錄:tsc 報 TS2339,getActiveToasts 不存在於 toast 型別;sonner 2.0.8 的 index.d.ts 只有 getHistory 與 getToasts;略過 tsc 執行後點按鈕得到 getActiveToasts is not a function

我另外把 auto2 只跑 vite build(跳過型別檢查)再開起來,用 Playwright 點「顯示 toast 數量」,頁面丟出 ue.getActiveToasts is not a function。也就是說,如果專案的 build 不跑 tsc,這個錯會一路帶到執行時才爆。

我翻了每組的 stream-json 紀錄,看它們是怎麼寫出那一行的:

  • auto2:載入 skill 後直接寫檔,過程只有一次 Glob 和兩次 Write,沒有去看 node_modules,也沒有自己跑建置。skill 寫什麼它就用什麼。
  • named1:skill 內容注入後,它還是去 grep 了 index.d.ts,發現沒有 getActiveToasts,改用 getToasts,自己跑 tsc -b 通過,最後的訊息寫明「已安裝的 sonner 是 2.0.8,沒有 getActiveToasts」。
  • auto1、without1、without2:都去讀了 node_modules/sonner 的型別或原始碼,自己找到 getToasts。
所以這次實驗裡,沒有 skill 的組別反而因為「不知道就去查」而寫對。skill 給了一個很確定的答案,模型有時就不查了。

skill 也有幫上忙的地方。點名的 named1 是 5 組裡唯一用 toast.promise 把「上傳中 → 成功 → 帶復原按鈕」寫成一個呼叫的:

toast.promise(fakeUpload(), {
  loading: '上傳中…',
  success: (fileName) => ({
    message: `上傳成功:${fileName}`,
    action: {
      label: '復原',
      onClick: () => toast('已復原'),
    },
  }),
  error: '上傳失敗',
})

這對應 API.md 對 toast.promise 的說明:「success/error accept strings, JSX, functions of the result, or objects of toast options」。其他 4 組(包括 auto2)都是先 toast.loading() 拿 id,再用 toast.success(..., { id }) 更新,也能動,只是多寫幾行。

深色模式那一題 5 組都寫了 <Toaster theme="system" richColors />,有沒有 skill 沒差。

下面是 named1 的實際畫面,用 Playwright 分別以淺色、深色模式開啟(為了截到成功的那一刻,我把 Math.random 固定成會成功)。左邊是成功 toast 帶「復原」按鈕,右邊是按下復原後跳出的「已復原」。按「顯示 toast 數量」在兩種模式下都 alert 出 1。

named1 的畫面:淺色與深色模式下,成功 toast 為綠色並帶復原按鈕,按下後出現已復原的 toast

這是 5 組各跑一次的結果,樣本很小,不能拿來算觸發率或錯誤率。它能說明的是:skill 裡的錯誤 API 確實會被照抄,而且照抄的那次沒有任何自我檢查。

⚙️ 怎麼安裝、怎麼點名?

只裝這一支、裝給 Claude Code,我實際跑過的指令(2026-10-21,skills@latest 解析到 1.7.4):

npx -y skills@latest add emilkowalski/skills --skill ask-sonner --agent claude-code --copy -y

不加 --agent 和 -y 會進互動選單:

npx skills@latest add emilkowalski/skills --skill ask-sonner

裝完專案裡會多出 .claude/skills/ask-sonner/SKILL.md、API.md 和一份 skills-lock.json。我把裝出來的兩個檔跟 repo 的 commit 2d22863 比對,內容一樣。安裝時 CLI 顯示的第三方評估是 Gen Safe、Socket 0 alerts、Snyk Low Risk。

點名的方式是把需求接在指令後面:

/ask-sonner 幫我把上傳流程改成 toast.promise,成功時帶一顆復原按鈕

🚧 適合誰?限制與坑

適合:專案已經用 Sonner、常遇到樣式蓋不掉或 toast 不出現的人。疑難排解表跟樣式升級梯子是作者本人的判斷,比 AI 自己猜可靠。剛要選 toast 函式庫的人,它也能幫你一次把 Toaster 放對位置。

不適合:沒用 Sonner 的專案。它不是通用的 toast 設計指南。

坑:

  • getActiveToasts 不能用。要在 React 外面讀目前的 toast,用 toast.getToasts()。這個錯在官方文件也有,所以不能靠「去看文件」來發現,要看型別檔。
  • offset 預設是 24px,不是 skill 和官方文件寫的 32px。要精確對齊設計稿時自己指定。
  • 沒點名不一定會觸發。我沒點名跑了 2 次,1 次有用上。
  • 用上了不代表 AI 會驗證。這次照抄錯 API 的那組,連建置都沒跑。我的做法是在需求最後加一句「寫完跑 npx tsc -b 確認能編譯」,或在 Claude Code 設一個寫檔後跑型別檢查的 hook。
  • API.md 不是完整的 props 表。有些 2.0.8 支援的選項它沒列,AI 可能因此繞遠路。需要完整清單時直接看 node_modules/sonner/dist/index.d.ts。

🔗 延伸資源

下一步:如果你的專案有 Sonner,裝好 ask-sonner 後先用 /ask-sonner 問一個你真的卡住的樣式問題,看它給的 classNames 或 headless 建議合不合用;要它寫程式時,記得叫它寫完跑一次 tsc。
author
陳
陳彥彤

AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

support

覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。