Toast-Benachrichtigungen in Svelte erstellen
Erstellen Sie Toast-Benachrichtigungen in Svelte mit einem writable Store oder svelte-sonner, inklusive Svelte 5-Syntax, Barrierefreiheit und Auto-Close.
Eine Toast-Benachrichtigung in Svelte ist eine kleine, kurzlebige Meldung, die über der UI erscheint, um eine Aktion zu bestätigen oder einen Fehler zu melden, und sich nach einem Timeout selbst wieder ausblendet.
Die meisten von uns schreiben so etwas irgendwann spätabends – direkt nachdem ein Formular erfolgreich abgeschickt wurde und die Seite einfach nur dasteht, als wäre nichts passiert. Es gibt zwei solide Wege: ein leichtgewichtiges System selbst bauen, mit einem writable Store plus einer Container-Komponente, oder eine gepflegte Bibliothek einbinden. Dieser Artikel zeigt beides mit Copy-and-paste-fähigem Code, behandelt Barrierefreiheit und weist darauf hin, wo ältere Svelte-4-Tutorials Syntax verwenden, die in Svelte 5 inzwischen deprecated ist.
Alles Folgende bezieht sich auf Svelte 5, die aktuelle stabile Hauptversion, stabil seit Oktober 2024. Wo sich Svelte 4 unterscheidet, wird der Unterschied direkt im Text vermerkt.
Die wichtigsten Erkenntnisse
- In Svelte 5 ändert sich der Toast-Store kaum (
writable([])funktioniert weiterhin), aber die Toast-Komponente muss migriert werden: ausexport letwird$props, auson:clickwirdonclick, aus<slot />wird ein Snippet, undcreateEventDispatcherwird durch eine Callback-Prop ersetzt. - Damit Screenreader Toasts ankündigen, müssen sie innerhalb eines Containers mit
aria-livegerendert werden (politefür Info/Erfolg,assertivefür Fehler).role="alert"auf jedem einzelnen Toast ist eine Alternative zu diesem Container, keine Ergänzung: Werden beide kombiniert, kann eine einzelne Meldung doppelt vorgelesen werden. - Geben Sie jedem Toast eine kollisionssichere ID mit
crypto.randomUUID()und löschen Sie seinen Auto-Dismiss-Timer, wenn er manuell entfernt wird – so löst ein von Hand geschlossener Toast niemals eine veraltete Entfernung aus. - svelte-sonner wird mit
npm i svelte-sonnerinstalliert, einmalig als<Toaster />im App-Root gerendert und anschließend von überall übertoast(),toast.success(),toast.error()odertoast.promise()ausgelöst. - Bauen Sie es selbst, wenn Sie null Abhängigkeiten und volle Kontrolle wollen; greifen Sie zu svelte-sonner, wenn Promise-Toasts, Swipe-to-dismiss, Theming und Barrierefreiheit von Haus aus abgedeckt sein sollen.
Was ist ein Toast, und wann sollte man ihn einsetzen?
Ein Toast ist kurzlebiges, nicht blockierendes Feedback: Erfolgs-, Fehler- oder Infomeldungen, die sich stapeln, nach einem Timer automatisch verschwinden und den Nutzer nie so unterbrechen wie ein Modal. Setzen Sie einen Toast ein, um das Absenden eines Formulars zu bestätigen, einen asynchronen Fehler sichtbar zu machen oder eine Hintergrundaktion zu quittieren. Verwenden Sie ihn nicht für Inhalte, auf die der Nutzer reagieren muss oder die er auf keinen Fall verpassen darf. Das gehört in eine Inline-Meldung oder einen Dialog, denn ein Toast kann verschwinden, bevor er gelesen wurde.
Wie baut man ein Toast-System in Svelte mit einem Store?
Discover how at OpenReplay.com.
Der Kern eines selbst gebauten Systems ist ein einzelner writable Store, der ein Array von Toast-Objekten hält, plus addToast/dismissToast-Helper, die von überall aufgerufen werden können. Svelte Stores funktionieren in Svelte 5 weiterhin, dieses Pattern ist also nicht deprecated. Der neuere Runes-Ansatz mit .svelte.ts ist idiomatisch, aber optional.
// 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));
}
Zwei Korrektheitsdetails sollte man hier richtig machen. Die IDs stammen aus crypto.randomUUID() statt aus Math.random(), damit sie nicht kollidieren können (die Methode läuft nur in einem Secure Context, also unter HTTPS oder localhost). Und der Timer jedes Toasts wird in einer Map nachgehalten und beim manuellen Schließen gelöscht, sodass ein Klick auf den Schließen-Button nie ein setTimeout zurücklässt, das auf einen bereits entfernten Toast zeigt.
Der Container rendert nun das Array, keyed nach id, und übergibt jedem Toast einen Dismiss-Callback:
<!-- 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>
Die Kindkomponente Toast.svelte verwendet durchgängig Svelte-5-Idiome: $props() für die Eingaben, onclick für das Event und eine Callback-Prop für das Schließen:
<!-- 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>
Binden Sie <Toasts /> einmalig in Ihr Root-Layout ein und lösen Sie Toasts dann von überall aus:
import { addToast } from '$lib/toast-store.js';
addToast({ message: 'Saved!', type: 'success' });
Svelte 4 vs. Svelte 5: Was sich an der Syntax geändert hat
Wenn Sie aus einem älteren dev.to-Tutorial kopieren: Der Store ist portabel, die Komponente nicht. In Svelte 5 wird export let durch $props ersetzt, aus on:click wird das Attribut onclick, und <slot /> weicht Snippets. Am wichtigsten: createEventDispatcher ist deprecated – ein Schließen-Button sollte eine Callback-Prop aufrufen (ondismiss?.()), statt ein Event zu dispatchen. Die Svelte-4-Variante von Toast.svelte würde mit export let type = 'info' und import { createEventDispatcher } beginnen und on:click={() => dispatch('dismiss')} verwenden – allesamt Patterns, die in einem Svelte-5-Projekt als deprecated gelten.
Varianten, Positionierung und Barrierefreiheit
Drei UX-Details trennen einen funktionierenden Toast von einem guten: Varianten, Transitions und Screenreader-Unterstützung. Varianten sind lediglich ein type-Feld, das auf Hintergrundfarben gemappt wird (info/success/error), die fade-Transition aus svelte/transition animiert Ein- und Ausblenden, und ein Container mit position: fixed und hohem z-index hält die Toasts über der Seite fixiert.
Barrierefreiheit verdient einen eigenen Blick. Es gibt zwei Wege, einen Toast ankündigen zu lassen, und Sie sollten sich für genau einen entscheiden. role="alert" auf jedem Toast impliziert aria-live="assertive", und Browser behandeln Alert-Knoten tatsächlich besonders: MDN weist darauf hin, dass ihr Inhalt in den meisten Fällen angekündigt wird, auch dann, wenn der Knoten erst nach dem Laden in die Seite eingefügt wird. Der Haken ist, dass das je nach Kombination von Browser und Screenreader variiert. Eine persistente Live Region, die bereits im DOM liegt, ist daher die vorhersehbarere Option – weshalb der Container im obigen Code aria-live="polite" trägt und der Toast selbst gar keine Rolle. Verwenden Sie polite für Info- und Erfolgsmeldungen, damit sich Ankündigungen hinter dem einreihen, was der Nutzer gerade tut, und schalten Sie einen Container (oder eine zweite Region) auf assertive für Fehler, die sofortige Aufmerksamkeit erfordern.
Die Kombination beider Ansätze ist der Fehler, den es zu vermeiden gilt. MDN warnt, dass aria-live zusammen mit role="alert" in VoiceOver unter iOS zu doppeltem Vorlesen führt, und ein assertiver Alert innerhalb einer politen Region provoziert dieselbe doppelte Ankündigung. Session Replays von Toast-Implementierungen zeigen häufig genau jenen Fehlerfall, bei dem ein Toast ausgelöst und automatisch geschlossen wurde, aber nie wahrgenommen wurde: keine Live Region bedeutete, dass nichts angekündigt wurde.
Stattdessen eine Bibliothek nutzen: svelte-sonner
svelte-sonner ist der Drop-in-Weg und ist für Svelte 5 gebaut. Es ist ein Svelte-Port von Emil Kowalskis Sonner und übernimmt dessen meinungsstarke Defaults. Paket installieren, ein einziges <Toaster /> nahe dem Root Ihrer App einhängen – und jeder Toast, den Sie von irgendwo sonst in der Codebasis auslösen, wird darin gerendert.
<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>
Der Gegenwert für die Abhängigkeit ist toast.promise(): Der Toast öffnet sich in einem Ladezustand und ersetzt sich selbst durch eine Erfolgs- oder Fehlermeldung, sobald das Promise aufgelöst ist. Das ist das eine Pattern, dessen Handarbeit wirklich mühsam ist:
toast.promise(saveEvent(), {
loading: 'Saving…',
success: (data) => `${data.name} saved!`,
error: 'Could not save'
});
<Toaster /> akzeptiert die Props position, richColors, closeButton und duration; für Tailwind stylen Sie die Toasts selbst, indem Sie ein toastOptions-Objekt mit unstyled: true plus einer classes-Map übergeben. Swipe-to-dismiss und Tastaturfokus (⌥/Alt + T) sind bereits eingebaut. npm i svelte-sonner löst auf einen 1.x-Build auf; der neueste Eintrag in den Release Notes des Projekts ist v1.1.1, der einen Bug behoben hat, bei dem Toasts, die nie ablaufen sollten, in dem Moment geschlossen wurden, in dem sie aktualisiert wurden.
Zwei Alternativen. svelte-french-toast sollte man kennen, doch das veröffentlichte Stable-Release stammt aus der Svelte-4-Ära, sodass Svelte-5-Nutzer einen Fork wie svelte-hot-french-toast benötigen. Die andere ist @zerodevx/svelte-toast, dessen aktuelle v0-Linie Peer Dependencies über Svelte 3, 4 und 5 hinweg deklariert.
Selbst bauen oder svelte-sonner: Wie entscheiden?
Bauen Sie selbst, wenn Sie null Abhängigkeiten, volle Kontrolle über das Markup wollen oder Svelte Stores lernen möchten; greifen Sie zu svelte-sonner, wenn Promise-Toasts, Swipe-to-dismiss, Theming und Barrierefreiheit von Haus aus erledigt sein sollen.
| Anforderung | Selbst bauen | svelte-sonner |
|---|---|---|
| Abhängigkeiten | Keine | Ein Paket |
| Kontrolle über das Markup | Vollständig | Über toastOptions (unstyled + classes) |
| Promise-Toasts | Selbst bauen | toast.promise() eingebaut |
| Swipe-to-dismiss | DIY | Eingebaut |
| Barrierefreiheit | aria-live selbst verdrahten | Erledigt |
| Svelte-5-fähig | Ja (mit Runes/Callback-Props) | Ja, nativ |
Unter den Bibliotheken zielt svelte-sonner direkt auf Svelte 5; das ursprüngliche svelte-french-toast stammt aus der Svelte-4-Ära, und die v0-Linie von @zerodevx/svelte-toast funktioniert über Svelte 3, 4 und 5 hinweg.
Beginnen Sie mit der Store-basierten Variante, wenn Ihre Anforderungen Erfolg/Fehler/Info mit Auto-Dismiss sind. Das sind vielleicht 60 Zeilen und bringt Ihnen das Store-Pattern bei. In dem Moment, in dem Sie promise-getriebenes Feedback oder Swipe-Gesten brauchen, installieren Sie svelte-sonner und löschen Ihren eigenen Code. Was auch immer Sie wählen: Verdrahten Sie zuerst die aria-live-Region; das ist das eine Detail, das man leicht überspringt und dessen Fehlen schwer auffällt.
FAQs
Funktioniert createEventDispatcher in Svelte 5 noch?
Es läuft weiterhin, ist in Svelte 5 aber deprecated: Bestehende Komponenten, die es nutzen, funktionieren also weiter, während neuer Code darauf verzichten sollte. Der offizielle Ersatz für das Auslösen von Events wie dem Schließen eines Toasts ist eine Callback-Prop, etwa das Übergeben einer ondismiss-Funktion und deren Aufruf per ondismiss?.() aus dem Schließen-Button. Die Svelte-Dokumentation nennt Callback-Props und die $host()-Rune als empfohlene Alternativen.
Sollte jeder Toast role='alert' verwenden, oder sollte der Container aria-live tragen?
Beide Ansätze können funktionieren, aber nutzen Sie einen davon und nicht beide. Browser behandeln role='alert' besonders und kündigen den Inhalt in den meisten Fällen an, selbst wenn der Knoten erst nach dem Laden der Seite eingefügt wird – das variiert allerdings je nach Kombination von Browser und Screenreader. Ein persistenter Container, der bereits im DOM existiert und aria-live trägt, ist die vorhersehbarere Option: aria-live='polite' für Info und Erfolg, 'assertive' für Fehler. Beides gleichzeitig zu tun riskiert eine doppelte Ankündigung, und MDN weist darauf hin, dass die Kombination von aria-live und role='alert' in VoiceOver unter iOS zu doppeltem Vorlesen führt.
Was ist der Unterschied zwischen svelte-sonner und svelte-french-toast für Svelte 5?
svelte-sonner zielt direkt auf Svelte 5 und installiert sich als 1.x-Build mit Promise-Toasts, Swipe-to-dismiss, richColors und einem Schließen-Button. Das veröffentlichte stabile svelte-french-toast stammt aus der Svelte-4-Ära und sein letztes Stable-Release liegt vor Svelte 5, sodass Svelte-5-Nutzer einen Fork wie svelte-hot-french-toast benötigen. Ein svelte-french-toast 2.0.0-alpha existiert, wurde aber nicht als stabiles npm-Release ausgeliefert.
Kann ich in Svelte 5 weiterhin einen writable Store für Toasts nutzen, oder muss ich auf Runes umsteigen?
Ein writable Store funktioniert in Svelte 5 weiterhin und ist nicht deprecated, ein toasts-Store aus writable([]) plus Add- und Dismiss-Helpern ist also vollständig valide. Runes in einer .svelte.ts-Datei sind das neuere idiomatische Pattern für gemeinsam genutzten reaktiven State, aber optional. Migrieren muss die Komponente, die den Store konsumiert – nicht der Store selbst.
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