Créer des notifications toast dans Svelte
Créez des notifications toast Svelte avec un store writable ou utilisez svelte-sonner, avec la syntaxe Svelte 5, laccessibilité et lauto-fermeture.
Une notification toast dans Svelte est un petit message éphémère qui s’affiche par-dessus votre interface pour confirmer une action ou signaler une erreur, puis disparaît de lui-même après un délai.
La plupart d’entre nous finissent par en écrire une tard dans la nuit, juste après qu’un formulaire a été soumis avec succès et que la page reste là, comme si rien ne s’était passé. Deux approches solides s’offrent à vous : construire vous-même un système léger avec un store writable et un composant conteneur, ou intégrer une bibliothèque maintenue. Cet article présente les deux avec du code prêt à copier-coller, aborde l’accessibilité et signale les endroits où les anciens tutoriels Svelte 4 utilisent une syntaxe désormais dépréciée dans Svelte 5.
Tout ce qui suit cible Svelte 5, la version majeure stable actuelle, stable depuis octobre 2024. Lorsque Svelte 4 diffère, la différence est signalée au fil du texte.
Points clés à retenir
- Dans Svelte 5, le store de toasts change à peine (
writable([])fonctionne toujours), mais le composant de toast doit être migré :export letdevient$props,on:clickdevientonclick,<slot />devient un snippet, etcreateEventDispatcherest remplacé par une prop de rappel (callback). - Pour que les lecteurs d’écran annoncent les toasts, affichez-les dans un conteneur doté d’
aria-live(politepour les messages d’information/succès,assertivepour les erreurs).role="alert"sur chaque toast est une alternative à ce conteneur, et non un complément : combiner les deux peut entraîner l’annonce d’un même message à deux reprises. - Attribuez à chaque toast un identifiant sans risque de collision avec
crypto.randomUUID()et effacez son minuteur de fermeture automatique lorsqu’il est supprimé manuellement, afin qu’un toast fermé à la main ne déclenche jamais une suppression obsolète. - svelte-sonner s’installe avec
npm i svelte-sonner, s’affiche une seule fois sous la forme<Toaster />à la racine de l’application, puis se déclenche n’importe où viatoast(),toast.success(),toast.error()outoast.promise(). - Développez votre propre solution si vous souhaitez zéro dépendance et un contrôle total ; tournez-vous vers svelte-sonner si vous voulez des toasts basés sur les promesses, la fermeture par balayage, la thématisation et l’accessibilité gérés d’emblée.
Qu’est-ce qu’un toast, et quand faut-il l’utiliser ?
Un toast est un retour d’information éphémère et non bloquant : des messages de succès/erreur/information qui s’empilent, disparaissent automatiquement après un délai et n’interrompent jamais l’utilisateur comme le fait une fenêtre modale. Utilisez un toast pour confirmer la soumission d’un formulaire, faire remonter une erreur asynchrone ou accuser réception d’une action en arrière-plan. N’en utilisez pas pour du contenu sur lequel l’utilisateur doit agir ou qu’il ne doit pas manquer. Cela relève d’un message en ligne ou d’une boîte de dialogue, car un toast peut disparaître avant d’avoir été lu.
Comment construire un système de toasts dans Svelte avec un store ?
Discover how at OpenReplay.com.
Le cœur d’un système fait main est un unique store writable contenant un tableau d’objets toast, accompagné des fonctions utilitaires addToast/dismissToast que vous pouvez appeler de n’importe où. Les stores Svelte fonctionnent toujours dans Svelte 5, ce motif n’est donc pas déprécié. L’approche plus récente avec les runes dans un fichier .svelte.ts est idiomatique, mais facultative.
// src/lib/toast-store.js
import { writable } from 'svelte/store';
export const toasts = writable([]);
const timers = new Map();
export function addToast(toast) {
const id = crypto.randomUUID();
const defaults = { id, type: 'info', dismissible: true, timeout: 3000 };
const t = { ...defaults, ...toast };
toasts.update((all) => [t, ...all]);
if (t.timeout) {
timers.set(id, setTimeout(() => dismissToast(id), t.timeout));
}
return id;
}
export function dismissToast(id) {
const timer = timers.get(id);
if (timer) {
clearTimeout(timer); // stop a stale auto-dismiss from firing later
timers.delete(id);
}
toasts.update((all) => all.filter((t) => t.id !== id));
}
Deux détails de correction méritent ici toute votre attention. Les identifiants proviennent de crypto.randomUUID() plutôt que de Math.random(), ce qui exclut toute collision (cette méthode ne fonctionne que dans un contexte sécurisé, c’est-à-dire en HTTPS ou sur localhost). Par ailleurs, le minuteur de chaque toast est suivi dans une Map et effacé lors d’une fermeture manuelle : cliquer sur le bouton de fermeture ne laisse donc jamais un setTimeout pointant vers un toast déjà supprimé.
Le conteneur affiche ensuite le tableau, indexé par id, et transmet à chaque toast un rappel de fermeture :
<!-- src/lib/Toasts.svelte -->
<script>
import Toast from './Toast.svelte';
import { toasts, dismissToast } from './toast-store.js';
</script>
<section class="toast-container" role="region" aria-live="polite" aria-label="Notifications">
{#each $toasts as toast (toast.id)}
<Toast {...toast} ondismiss={() => dismissToast(toast.id)} />
{/each}
</section>
<style>
.toast-container {
position: fixed; top: 1rem; left: 0; right: 0;
display: flex; flex-direction: column; align-items: center;
gap: 0.5rem; z-index: 1000; pointer-events: none;
}
</style>
Le composant enfant Toast.svelte emploie les idiomes Svelte 5 de bout en bout : $props() pour les entrées, onclick pour l’événement, et une prop de rappel pour la fermeture :
<!-- src/lib/Toast.svelte (Svelte 5) -->
<script>
import { fade } from 'svelte/transition';
let { message, type = 'info', dismissible = true, ondismiss } = $props();
</script>
<article class="toast {type}" transition:fade>
<p>{message}</p>
{#if dismissible}
<button class="close" onclick={() => ondismiss?.()} aria-label="Dismiss notification">×</button>
{/if}
</article>
<style>
.toast { display: flex; gap: 1rem; width: 20rem; padding: 0.75rem 1.25rem;
border-radius: 0.25rem; color: white; pointer-events: auto; }
.info { background: SteelBlue; }
.success { background: SeaGreen; }
.error { background: IndianRed; }
.close { margin-left: auto; background: none; border: 0; color: inherit;
font-size: 1.25rem; cursor: pointer; }
</style>
Montez <Toasts /> une seule fois dans votre layout racine, puis déclenchez les toasts depuis n’importe où :
import { addToast } from '$lib/toast-store.js';
addToast({ message: 'Saved!', type: 'success' });
Svelte 4 vs Svelte 5 : la syntaxe qui a changé
Si vous copiez un ancien tutoriel dev.to, le store est portable, mais pas le composant. Dans Svelte 5, export let est remplacé par $props, on:click devient l’attribut onclick, et <slot /> est remplacé par les snippets. Plus important encore, createEventDispatcher est déprécié : un bouton de fermeture doit appeler une prop de rappel (ondismiss?.()) plutôt que d’émettre un événement. La version Svelte 4 de Toast.svelte commencerait par export let type = 'info', import { createEventDispatcher }, et utiliserait on:click={() => dispatch('dismiss')} — autant de motifs dépréciés dans un projet Svelte 5.
Variantes, positionnement et accessibilité
Trois détails d’expérience utilisateur distinguent un toast fonctionnel d’un bon toast : les variantes, les transitions et la prise en charge des lecteurs d’écran. Les variantes ne sont qu’un champ type associé à des couleurs de fond (info/success/error), la transition fade de svelte/transition anime l’entrée et la sortie, et un conteneur en position: fixed avec un z-index élevé maintient les toasts épinglés au-dessus de la page.
L’accessibilité mérite qu’on s’y arrête. Vous disposez de deux moyens pour faire annoncer un toast, et vous devez en choisir exactement un. role="alert" sur chaque toast implique aria-live="assertive", et les navigateurs accordent effectivement un traitement particulier aux nœuds d’alerte : MDN indique que leur contenu est annoncé dans la plupart des cas, y compris lorsque le nœud est injecté dans la page après le chargement. Le hic, c’est que cela varie selon le couple navigateur/lecteur d’écran ; une région live persistante, déjà présente dans le DOM, est donc l’option la plus prévisible. C’est pourquoi le conteneur du code ci-dessus porte aria-live="polite" et le toast lui-même ne porte aucun rôle. Utilisez polite pour les messages d’information et de succès, afin que les annonces se placent en file d’attente derrière ce que fait l’utilisateur, et passez un conteneur (ou une seconde région) en assertive pour les erreurs qui nécessitent une attention immédiate.
L’erreur à éviter est de combiner les deux. MDN prévient que l’association d’aria-live et de role="alert" provoque une double énonciation dans VoiceOver sur iOS, et une alerte assertive affichée à l’intérieur d’une région polie expose au même doublon d’annonce. Les rejeux de session (session replays) d’implémentations de toasts révèlent fréquemment le mode de défaillance suivant : un toast s’est déclenché puis a disparu automatiquement sans jamais avoir été perçu — l’absence de région live signifiait que rien n’avait été annoncé.
Utiliser une bibliothèque à la place : svelte-sonner
svelte-sonner est la solution prête à l’emploi, et elle est conçue pour Svelte 5. Il s’agit d’un portage Svelte de Sonner, la bibliothèque d’Emil Kowalski, dont elle reprend les mêmes valeurs par défaut assumées. Installez le paquet, montez un unique <Toaster /> près de la racine de votre application, et chaque toast que vous déclenchez depuis n’importe quel autre endroit de la base de code s’affichera à l’intérieur.
<script>
import { Toaster, toast } from 'svelte-sonner';
</script>
<Toaster richColors closeButton position="top-center" duration={5000} />
<button onclick={() => toast.success('Event has been created')}>Success</button>
<button onclick={() => toast.error('Event has not been created')}>Error</button>
La dépendance se justifie par toast.promise(), qui s’ouvre dans un état de chargement puis se remplace par un message de succès ou d’erreur dès que la promesse est résolue. C’est le seul motif véritablement fastidieux à implémenter soi-même :
toast.promise(saveEvent(), {
loading: 'Saving…',
success: (data) => `${data.name} saved!`,
error: 'Could not save'
});
<Toaster /> accepte les props position, richColors, closeButton et duration ; avec Tailwind, vous stylisez vous-même les toasts en passant un objet toastOptions contenant unstyled: true et une table classes. La fermeture par balayage et le focus clavier (⌥/alt + T) sont intégrés d’office. npm i svelte-sonner installe une version 1.x ; la dernière entrée des notes de version du projet est la v1.1.1, qui a corrigé un bug où les toasts configurés pour ne jamais expirer disparaissaient dès leur mise à jour.
Deux alternatives. svelte-french-toast mérite d’être connue, mais sa version stable publiée date de l’ère Svelte 4 ; les utilisateurs de Svelte 5 ont donc besoin d’un fork tel que svelte-hot-french-toast. L’autre est @zerodevx/svelte-toast, dont la branche v0 actuelle déclare des dépendances de pair couvrant Svelte 3, 4 et 5.
Faire soi-même ou utiliser svelte-sonner : comment choisir
Développez votre propre solution si vous voulez zéro dépendance, un contrôle total sur le balisage, ou apprendre les stores Svelte ; tournez-vous vers svelte-sonner si vous voulez des toasts basés sur les promesses, la fermeture par balayage, la thématisation et l’accessibilité gérés d’emblée.
| Besoin | Solution maison | svelte-sonner |
|---|---|---|
| Dépendances | Aucune | Un paquet |
| Contrôle du balisage | Total | Via toastOptions (unstyled + classes) |
| Toasts basés sur les promesses | À implémenter soi-même | toast.promise() intégré |
| Fermeture par balayage | À faire soi-même | Intégrée |
| Accessibilité | Vous câblez aria-live vous-même | Gérée |
| Compatible Svelte 5 | Oui (avec les runes / props de rappel) | Oui, nativement |
Parmi les bibliothèques, svelte-sonner cible directement Svelte 5 ; la svelte-french-toast originale relève de l’ère Svelte 4, et la branche v0 de @zerodevx/svelte-toast fonctionne avec Svelte 3, 4 et 5.
Commencez par la version basée sur un store si vos besoins se limitent aux messages de succès/erreur/information avec fermeture automatique. Cela représente une soixantaine de lignes et vous apprend le motif des stores. Dès que vous avez besoin d’un retour piloté par les promesses ou de gestes de balayage, installez svelte-sonner et supprimez votre code personnalisé. Quel que soit votre choix, mettez d’abord en place la région aria-live : c’est le détail qu’on oublie facilement et dont l’absence passe difficilement inaperçue… jusqu’à ce qu’il soit trop tard.
FAQ
createEventDispatcher fonctionne-t-il encore dans Svelte 5 ?
Il fonctionne toujours mais est déprécié dans Svelte 5 : les composants existants qui l'utilisent continuent donc de fonctionner, tandis que le nouveau code ne devrait pas l'adopter. Le remplacement officiel pour émettre des événements comme la fermeture d'un toast est une prop de rappel, par exemple en passant une fonction ondismiss et en appelant ondismiss?.() depuis le bouton de fermeture. La documentation Svelte présente les props de rappel et la rune $host() comme les alternatives recommandées.
Chaque toast doit-il utiliser role='alert', ou le conteneur doit-il utiliser aria-live ?
Les deux approches peuvent fonctionner, mais utilisez l'une et pas les deux. Les navigateurs accordent un traitement particulier à role='alert' et, dans la plupart des cas, annoncent son contenu même lorsque le nœud est inséré après le chargement de la page, bien que cela varie selon le couple navigateur/lecteur d'écran. Un conteneur persistant, déjà présent dans le DOM et porteur d'aria-live, est l'option la plus prévisible : aria-live='polite' pour les messages d'information et de succès, 'assertive' pour les erreurs. Faire les deux à la fois risque de produire une annonce en double, et MDN indique que la combinaison d'aria-live et de role='alert' provoque une double énonciation dans VoiceOver sur iOS.
Quelle est la différence entre svelte-sonner et svelte-french-toast pour Svelte 5 ?
svelte-sonner cible directement Svelte 5 et s'installe en version 1.x avec les toasts basés sur les promesses, la fermeture par balayage, richColors et un bouton de fermeture. La version stable publiée de svelte-french-toast relève de l'ère Svelte 4 et sa dernière version stable est antérieure à Svelte 5 ; les utilisateurs de Svelte 5 ont donc besoin d'un fork tel que svelte-hot-french-toast. Une version 2.0.0-alpha de svelte-french-toast existe, mais n'a pas été publiée comme version stable sur npm.
Puis-je continuer à utiliser un store writable pour les toasts dans Svelte 5, ou dois-je passer aux runes ?
Un store writable fonctionne toujours dans Svelte 5 et n'est pas déprécié : un store toasts construit avec writable([]) et des fonctions utilitaires d'ajout et de fermeture est donc parfaitement valide. Les runes dans un fichier .svelte.ts constituent le motif idiomatique plus récent pour l'état réactif partagé, mais elles sont facultatives. C'est le composant qui consomme le store qui doit migrer vers la syntaxe Svelte 5, et non le store lui-même.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k