--- name: ask-sonner description: 'Orienta integração e auditoria de Sonner em React: Toaster, toast(), promise/loading, update/dismiss, styling, theming, posição e múltiplos toasters. Use quando Sonner já existe ou foi selecionado; não use para escolher biblioteca sem antes passar por pick-ui-library.' metadata: portfolio-design-auditor-role: implementation-support adaptation-version: 0.1.0 source: user-provided-skills-zip --- # Working With Sonner ## O que faz Resolve setup, uso, styling e troubleshooting de Sonner, mantendo uma única fonte de verdade para toasts e evitando duplicação, layering incorreto e estados assíncronos inconsistentes. ## Quando usar Use quando o projeto já usa Sonner ou uma decisão aprovada escolheu Sonner para feedback transitório. Consulte `references/API.md` quando for necessário confirmar nomes de props, tipos ou defaults. ## Quando não usar Não usar para escolher Sonner por preferência pessoal; não introduzir toast quando feedback inline é mais apropriado; não usar toast para erros persistentes ou informação crítica que precisa permanecer visível; não duplicar `` sem motivo explícito. ## Formato / contrato de uso **Entrada:** caso de feedback, implementação atual, versão instalada quando verificável, constraints de tema/layering e accessibility. **Saída:** padrão Sonner recomendado, chamada/API exata quando verificada, estratégia de lifecycle (loading/promise/update/dismiss), styling/theme e validação. **Pós-condição:** confirmar foco/semântica/contexto do feedback e que o toast não substitui uma mensagem persistente necessária. ## Integração no Portfolio Design Auditor Esta skill participa do pipeline: `CONTEXT → AUDIT → APPROVAL → PLAN → IMPLEMENT → VALIDATE → REPORT` Respeite estas regras compartilhadas: - Preserve a identidade visual, o design system e a linguagem de motion existentes quando não houver finding aprovado que justifique mudança. - Separe observação de inferência e preferência. - Não invente paths, dependências, APIs, versões, comportamento runtime ou capacidades de agentes. - Durante auditoria, source code permanece read-only. - Implementação exige finding/requisito aprovado e scope explícito. - Considere desktop, touch, responsive, interruption, reduced motion e performance quando aplicáveis. - Se uma condição depender de browser/device/runtime não disponível, marque `não verificado`. - Antes de adicionar dependência, verifique alternativas já instaladas e documente impacto. - Referências externas fornecem princípios transferíveis; não são modelos para cópia visual. ## Base de conhecimento adaptada O conteúdo abaixo preserva a orientação técnica da skill fornecida pelo usuário, com o gate client-specific de “Initial Response” removido para permitir composição portátil dentro deste plugin. ## Setup Two pieces, and only two: 1. **One ``, 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 ``` ## Picking the right call | You want | Call | | --- | --- | | 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()` | | Custom JSX, no styles at all | `toast.custom((t) => )` — 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(() => View)`. **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: `` from `next-themes`. ## Troubleshooting | Symptom | Cause → fix | | --- | --- | | Toast never appears | No `` 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 twice | Two 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 effect | Default 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 DOM | Styles 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 clipped | An ancestor creates a stacking context (`transform`, `filter`, `overflow`) or the overlay out-z-indexes the toaster. Move `` 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/red | That'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 work | Directions derive from `position`. Override with `swipeDirections` on the Toaster. | | Toast shows up in every toaster | Multiple 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. |