React
Toast con Sonner: scelte e trappole
Monta un solo Toaster nel layout e chiama toast() dagli eventi, mai dal render: Sonner annuncia il messaggio, ma durata e contesto restano una tua scelta.
In sintesi
- Un solo `
` per applicazione: due istanze annunciano lo stesso messaggio due volte. - Chiama `toast()` da un gestore di eventi o da un effetto, mai durante il render.
- La durata predefinita è di 4 secondi: i messaggi importanti hanno bisogno di `closeButton` o di più tempo.
Risposta breve: installa il componente con npx shadcn@latest add sonner, monta <Toaster /> una volta nel layout e chiama toast() da un gestore di eventi. Sonner gestisce coda, animazione e regione live; a te restano posizione, durata e tono del messaggio.
Il montaggio: una volta sola
Il Toaster è il contenitore che disegna i messaggi. Va nel layout radice, dopo i children, e non nelle singole pagine. Due istanze nello stesso albero creano due regioni live, quindi lo screen reader annuncia lo stesso testo due volte.
// app/layout.tsx
import { Toaster } from "@/components/ui/sonner";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
Il file è un client component, ma il layout può restare un server component: basta importarlo. position accetta top-left, top-center, top-right, bottom-left, bottom-center e bottom-right; richColors colora lo sfondo per esito; closeButton aggiunge la X.
Le tre chiamate che coprono quasi tutto
La chiamata secca serve per un'informazione, toast.success e toast.error per un esito, toast.promise quando l'esito arriva da una promise. Il caso promise è quello che evita più codice: Sonner sostituisce lo stesso toast nei tre stati invece di impilarne tre.
"use client";
import { toast } from "sonner";
export function SaveButton() {
async function onSubmit() {
await toast.promise(saveDraft(), {
loading: "Salvataggio in corso…",
success: "Bozza salvata",
error: (error) => `Salvataggio fallito: ${error.message}`,
});
}
return Salva;
}
Le trappole che si vedono in produzione
| Trappola | Cosa succede | Correzione |
|---|---|---|
| <Toaster /> in ogni pagina | Ogni mount aggiunge una regione live e il messaggio viene annunciato più volte. | Una sola istanza nel layout radice. |
| toast() nel corpo del componente | Parte un toast per ogni render, compresi quelli di StrictMode in sviluppo. | Chiamata dentro un handler o dentro un effetto con dipendenze esplicite. |
| toast.error(error) | L'oggetto viene convertito in stringa e il messaggio diventa illeggibile. | Passa un testo: toast.error(error.message). |
| Durata molto lunga | Il toast copre i controlli in basso a destra e non scade mai. | 4-6 secondi, oppure closeButton e dismiss manuale. |
| Tema non passato al Toaster | I toast restano chiari su un'interfaccia scura. | Leggi il tema dal provider e passalo a theme, oppure usa toastOptions. |
Accessibilità: il tempo è parte del messaggio
Sonner scrive ogni toast in una regione live: il testo viene annunciato e il focus resta dov'era. Il problema nasce dalla durata. Un messaggio che sparisce dopo 4 secondi non rispetta il criterio WCAG 2.2.1 se contiene l'unica azione disponibile, perché l'utente non fa in tempo a raggiungerla. Per errori e azioni distruttive usa closeButton e una durata più alta, oppure lascia il messaggio finché non viene chiuso.
Limite dichiarato
Sonner disegna i toast sopra il documento e non partecipa al layout: un test end-to-end che cerca il messaggio dentro il contenitore della pagina non lo trova. Il catalogo, misurato il 1 novembre 2026, non ha blocchi toast da copiare, perché un blocco è una composizione statica e un toast esiste solo a runtime. Il componente è documentato nel record sonner, non in un blocco.
Dove continuare
Nel catalogo trovi la libreria shadcn, il record del componente sonner e un secondo record toast. Per l'installazione leggi che cosa è shadcn/ui e come installare shadcn/ui; per capire quanto costa aggiungere blocchi al catalogo, 1.556 blocchi tra preview e Pro.
Domande frequenti
Dove va montato il Toaster?
Una volta sola, nel layout radice, dopo i children. Due istanze nello stesso albero annunciano lo stesso messaggio due volte e confondono chi usa lo screen reader.
Perché il toast compare due volte?
Quasi sempre per due istanze di Toaster, oppure per una chiamata dentro un effetto senza guardia: in sviluppo StrictMode esegue l'effetto due volte. Controlla il layout prima di cambiare la logica.
Posso chiamare toast() da un Server Component?
No. toast() scrive nel DOM e Sonner è un componente client: la chiamata deve stare in un file con "use client" o dentro un handler, non nel corpo di un server component.
Come mostro l'esito di una promise?
Usa toast.promise(promise, { loading, success, error }). Sonner riusa lo stesso toast per i tre stati e passa l'errore alla funzione error, che deve restituire una stringa.
Quanto deve durare un toast?
Quattro secondi vanno bene per un'informazione. Se il messaggio contiene un'azione o un errore, aggiungi closeButton e allunga la durata, altrimenti violi il criterio WCAG 2.2.1 sul tempo.