Travailler avec Sonner
Réponse initiale
Lorsque cette compétence est invoquée pour la première fois sans question spécifique, répondez uniquement avec :
Je suis prêt à vous aider à configurer, styliser et dépanner Sonner, mes connaissances proviennent de son auteur, Emil Kowalski.
Ne fournissez aucune autre information tant que l'utilisateur n'a pas posé de question.
Un guide de compétence pour Sonner, la bibliothèque de toasts. Lorsqu'une tâche implique Sonner — le configurer, afficher des toasts, les styliser ou les réparer — répondez d'abord à partir de ce fichier. Les tableaux de propriétés complets pour <Toaster /> et toast() se trouvent dans API.md ; lisez-le lorsque vous avez besoin d'un nom de propriété, d'un type ou d'une valeur par défaut exacts.
Configuration
Deux éléments, et seulement deux :
- Un
<Toaster />, monté une fois, aussi près de la racine que possible (dans Next.js :layout.tsx— il fonctionne à l'intérieur des composants serveur). Ne le rendez jamais par page ou de manière conditionnelle ; un deuxième Toaster monté duplique chaque toast. toast()appelé depuis le code client — gestionnaires d'événements, effets, callbacks. C'est une simple fonction, pas besoin de hook ou de fournisseur, mais elle ne fait rien côté serveur : dans une action serveur, retournez le résultat et appeleztoast()dans le code client qui le reçoit.
import { Toaster } from 'sonner'; // une fois, dans layout
import { toast } from 'sonner'; // n'importe où côté client
Choisir le bon appel
| Vous voulez | Appel |
|---|---|
| Message simple | toast('Titre') — ajoutez { description } pour une deuxième ligne |
| Icône succès / erreur / info / avertissement | toast.success('…'), toast.error('…'), etc. |
| Spinner pendant que vous gérez l'état vous-même | toast.loading('…'), puis mettez-le à jour par son id |
| Chargement → succès/erreur lié à une promesse | toast.promise(promise, { loading, success, error }) — success/error acceptent des fonctions recevant la valeur résolue / l'erreur |
| Bouton qui fait quelque chose | { action: { label, onClick } } — ferme le toast à moins que onClick n'appelle event.preventDefault() ; cancel est la variante secondaire |
| JSX personnalisé, coque de toast par défaut | toast(<jsx />) |
| JSX personnalisé, sans aucun style | toast.custom((t) => <jsx />) — headless, t vous donne l'id pour le supprimer |
Recettes
Mettre à jour un toast — appelez toast() à nouveau avec le même id ; seules les propriétés que vous passez changent. Passer à toast.success(…, { id }) change le type. C'est ainsi que fonctionnent les flux chargement → succès sans toast.promise :
const id = toast.loading('Téléchargement…');
toast.success('Téléchargé', { id });
Persister — { duration: Infinity }. Supprimer — toast.dismiss(id), ou toast.dismiss() pour tous. Lire les toasts actifs — useSonner() dans React, toast.getActiveToasts() en dehors.
Liens ou composants dans le texte — passez une fonction pour le titre ou la description : toast(() => <a href="…">Voir</a>).
Toasters multiples — donnez à chacun un id et ciblez avec toast('…', { toasterId: 'canvas' }). Sans toasterId, chaque toaster affiche le toast.
Callbacks de fermeture — onDismiss se déclenche sur le bouton de fermeture ou le balayage ; onAutoClose se déclenche sur expiration du délai. Ils sont séparés ; il n'existe pas de callback unique « fermé ».
Style — l'échelle d'escalade
Ne montez que selon ce que le changement nécessite ; sauter trop tôt à l'échelon supérieur est acceptable (c'est l'état final recommandé), s'attarder au milieu ne l'est pas.
- Valeurs par défaut — plus
richColorssur le Toaster pour des couleurs vives en succès/erreur,invertpour inverser selon le thème. - Ajustements en ligne —
toastOptions={{ style: {…} }}sur le Toaster pour tous les toasts, oustylepar appeltoast(). - Classes sur les parties —
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}. Les styles injectés par Sonner gagnent la cascade, donc chaque classe a besoin de!important(Tailwind :!text-red-900). Si vous marquez plus de quelques éléments important, arrêtez — passez en headless. - Headless —
toast.custom()avec votre propre JSX, en conservant le positionnement, l'empilement et le balayage de Sonner. L'approche recommandée pour un toast de système de design : enveloppez-le dans votre propre abstractiontoast(). (unstyled: trueexiste comme solution intermédiaire, mais headless offre plus de contrôle pour le même effort.)
Icônes — remplacez les valeurs par défaut par type avec la propriété icons du Toaster, par toast avec icon, supprimez avec null.
Thème — theme par défaut à 'light' et ne suit pas l'OS. Passez theme="system", ou connectez votre fournisseur de thème : <Toaster theme={resolvedTheme} /> depuis next-themes.
Dépannage
| Symptôme | Cause → correctif |
|---|---|
| Le toast n'apparaît jamais | Aucun <Toaster /> monté, ou il a été démonté (rendu conditionnel, placement par page). Montez-en un à la racine. Si appelé depuis une action serveur : toast() est côté client uniquement — appelez-le avec le résultat de l'action sur le client. |
| Le même toast apparaît deux fois | Deux Toasters montés (layout et page) — gardez-en un. Ou toast() déclenché dans un effet sous la double invocation de développement de React StrictMode — déclenchez-le depuis le gestionnaire d'événements à la place, ou passez un id stable pour que le deuxième appel mette à jour plutôt que duplique. |
| Les classes Tailwind/CSS n'ont aucun effet | Les styles par défaut les écrasent. Marquez-les !important, ou utilisez unstyled / headless (voir l'échelle ci-dessus). |
| Les toasts s'affichent complètement sans style (courant dans Astro, transitions de vue) | La feuille de style injectée par Sonner a été perdue — importez-la explicitement dans un layout : import 'sonner/dist/styles.css'. |
| Sans style dans Shadow DOM | Les styles atterrissent dans document.head, pas dans la racine fantôme. Copiez la balise style dont le texte inclut [data-sonner-toaster] dans la racine fantôme. |
| Toast derrière une modale / superposition, ou rogné | Un ancêtre crée un contexte d'empilement (transform, filter, overflow) ou la superposition a un indice z supérieur au toaster. Déplacez <Toaster /> à la racine du document, en dehors de tout conteneur de boîte de dialogue / portail. |
| Mode sombre ignoré | theme par défaut à 'light' — définissez theme="system" ou passez le thème résolu (voir Thème ci-dessus). |
| Succès/erreur semblent gris, pas vert/rouge | C'est la valeur par défaut. Ajoutez richColors au Toaster. |
| Le toast ne se ferme jamais | duration: Infinity, dismissible: false, ou une toast.promise dont la promesse ne se règle jamais — le toast de chargement attend indéfiniment. |
toast.promise bloqué sur chargement | Il a besoin d'une promesse (ou d'une fonction en retournant une) comme premier argument, et la promesse doit effectivement se résoudre / échouer. |
| Le balayage pour supprimer va dans la mauvaise direction / ne fonctionne pas | Les directions dérivent de position. Remplacez avec swipeDirections sur le Toaster. |
| Le toast apparaît dans chaque toaster | Les toasters multiples nécessitent un ciblage : donnez à chaque Toaster un id et passez toasterId dans l'appel toast(). |
| Toasts trop près du bord de l'écran sur mobile | offset (bureau, par défaut 32px) et mobileOffset (<600px, par défaut 16px) — nombres, chaînes CSS, ou objets par côté. |