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

TrappolaCosa succedeCorrezione
<Toaster /> in ogni paginaOgni mount aggiunge una regione live e il messaggio viene annunciato più volte.Una sola istanza nel layout radice.
toast() nel corpo del componenteParte 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 lungaIl toast copre i controlli in basso a destra e non scade mai.4-6 secondi, oppure closeButton e dismiss manuale.
Tema non passato al ToasterI 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.