ask-sonner

โดย emilkowalski

คู่มือสำหรับ Sonner ไลบรารี toast ของ React — ติดตั้งและเชื่อมต่อ Toaster เลือกการเรียกใช้ toast() ที่เหมาะสม toast แบบ promise และ loading การอัปเดต การปิด และการคง toast ไว้ การจัดสไตล์ การทำธีม และไอคอน การจัดตำแหน่งและ toaster หลายตัว ใช้เมื่อทำงานกับ Sonner หรือแก้ไขปัญหา — toast ที่ไม่แสดงผล แสดงซ้ำ สูญเสียสไตล์ ไม่สนใจ Tailwind classes อยู่ด้านหลัง modal หรือไม่เป็นไปตาม dark mode

npx skills add https://github.com/emilkowalski/skills --skill ask-sonner

Working With 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.

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.
import { Toaster } from 'sonner'; // once, in layout
import { 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:

const id = toast.loading('Uploading…');
toast.success('Uploaded', { id });

Persist{ duration: Infinity }. Dismisstoast.dismiss(id), or toast.dismiss() for all. Read active toastsuseSonner() 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 callbacksonDismiss 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 tweakstoastOptions={{ style: {…} }} on the Toaster for all toasts, or style per toast() call.
  3. Classes on partstoastOptions={{ 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. Headlesstoast.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.

Themetheme 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.

Skills เพิ่มเติมจาก emilkowalski

find-animation-opportunities
emilkowalski
ค้นหาโค้ดเบสหรือ UI เพื่อหาจุดที่ควรมีแอนิเมชันแต่ไม่มี และปฏิเสธทุกอย่างที่ไม่ควรมี อ่านอย่างเดียวเท่านั้น เสนอการเคลื่อนไหวพร้อมค่าที่แม่นยำ แต่ไม่ได้ลงมือปรับใช้ ใช้เมื่อผู้ใช้ถามว่า "ตรงไหนที่ควรทำแอนิเมชันได้บ้าง?" หรือต้องการ "ทำให้ดูมีชีวิตชีวามากขึ้น" สำหรับการแก้แอนิเมชันที่มีอยู่ ให้ใช้ improve-animations หรือ review-animations แทน
developmentdesigncreative
animate
emilkowalski
สร้างแอนิเมชันตั้งแต่เริ่มต้น โดยตัดสินใจตามลำดับที่กำหนดว่ามันให้ความรู้สึกถูกต้องหรือไม่ — ควรจะมีการเคลื่อนไหวหรือไม่, มีวัตถุประสงค์อะไร, ใช้เครื่องมือใด, ใช้คุณสมบัติใด, ใช้ curve และระยะเวลาเท่าใด, จะขัดจังหวะอย่างไร, จะจบลงอย่างไร เขียนการนำไปใช้งาน ใช้เมื่อถูกขอให้สร้างแอนิเมชันบางอย่าง เพิ่มการเคลื่อนไหว ทำให้คอมโพเนนต์รู้สึกมีชีวิต หรือสร้างทรานซิชัน สำหรับการวิจารณ์การเคลื่อนไหวที่มีอยู่ให้ใช้ review-animations; สำหรับการตรวจสอบโค้ดเบสทั้งหมดให้ใช้ improve-animations
pick-ui-library
emilkowalski
เลือกไลบรารีที่เหมาะสมสำหรับงาน frontend ที่กำหนดจากรายการที่คัดสรรมาแล้วอย่างมีหลักการ — ตัวเลข, ช่องกรอก OTP, กราฟ, เมนูคำสั่ง, virtualization, drag and drop, toasts, state, styling และอื่นๆ อีกมากมาย ทำงานเฉพาะเมื่อถูกเรียกใช้อย่างชัดเจนเท่านั้น ไม่ทำงานเองโดยอัตโนมัติ
prototype
emilkowalski
สร้างเวอร์ชันที่แตกต่างกันอย่างแท้จริงหลายเวอร์ชันของชิ้นส่วน UI ที่คุณอธิบาย โดยแสดงผลไว้ด้านหลังตัวเลือกแบบภาพเพื่อให้คุณสามารถสลับดูได้แบบเรียลไทม์และเลื่อนระดับเวอร์ชันที่รู้สึกว่าเหมาะสมที่สุด ทำงานเฉพาะเมื่อถูกเรียกใช้อย่างชัดเจนเท่านั้น ไม่มีการทำงานเองโดยอัตโนมัติ
developmentdesigncreative
emil-design-eng
emilkowalski
ทักษะนี้เข้ารหัสปรัชญาของ Emil Kowalski เกี่ยวกับการตกแต่ง UI การออกแบบคอมโพเนนต์ การตัดสินใจเรื่องแอนิเมชัน และรายละเอียดที่มองไม่เห็นซึ่งทำให้ซอฟต์แวร์รู้สึกดี
designdevelopmentcreative
review-animations
emilkowalski
ตรวจสอบโค้ดแอนิเมชันและการเคลื่อนไหวเทียบกับมาตรฐานงานฝีมือระดับสูงที่ได้จากปรัชญาวิศวกรรมการออกแบบของ Emil Kowalski โดยค่าเริ่มต้นจะตั้งค่าสถานะไว้ก่อน การอนุมัติต้องได้รับจากการพิสูจน์
animation-vocabulary
emilkowalski
พจนานุกรมค้นหาย้อนกลับที่เปลี่ยนคำอธิบายคลุมเครือของแอนิเมชันเว็บหรือเอฟเฟกต์การเคลื่อนไหวให้เป็นคำศัพท์ที่แน่นอน ("สิ่งกระเด้งเมื่อป๊อปโอเวอร์เปิด" → Pop in; "การเลื่อนแบบยางรัดของ iOS" → Rubber-banding) ใช้เมื่อผู้ใช้ถามว่า "มันเรียกว่าอะไรเมื่อ…" หรืออธิบายเอฟเฟกต์การเคลื่อนไหวโดยไม่รู้ชื่อ และต้องการคำที่ถูกต้องเพื่อบอก AI หรือนักออกแบบ สำหรับการตั้งชื่อเอฟเฟกต์ ไม่ใช่การออกแบบหรือสร้างขึ้นมา
creativedesignresearch
improve-animations
emilkowalski
สำรวจโค้ดเบสด้านแอนิเมชันและโค้ดการเคลื่อนไหวในฐานะที่ปรึกษาด้านการเคลื่อนไหวระดับอาวุโส จากนั้นสร้างรายการตรวจสอบตามลำดับความสำคัญและแผนการดำเนินงานแบบครบวงจรให้กับเอเจนต์อื่น (หรือโมเดลที่ราคาถูกกว่า) นำไปปฏิบัติ อ่านได้อย่างเดียวจากซอร์สโค้ด — วางแผนการปรับปรุง แต่ไม่นำไปใช้ ใช้เมื่อผู้ใช้ขอให้ "ปรับปรุงแอนิเมชัน" "ตรวจสอบการเคลื่อนไหว" "ทำให้แอปนี้รู้สึกดีขึ้น" หรือต้องการแผนงานแก้ไขแอนิเมชันมากกว่าการตรวจสอบ diff เพียงครั้งเดียว