Events haben sich in Svelte 5 spürbar geändert: DOM-Events sind ganz normale HTML-Attribute (onclick={...} statt on:click={...}), Component-Events werden als Callback-Props modelliert statt mit createEventDispatcher und die Modifier-Syntax (|preventDefault|once) ist entfallen. Die neue Form ist konsistenter, einfacher zu typisieren und verhält sich identisch zum DOM. Dieser Artikel zeigt alle Varianten und die Migration aus Svelte 4.
DOM-Events
Jeder Event-Handler ist ein normales Attribut:
<script>
function handleClick(event) {
console.log('clicked', event.target);
}
</script>
<button
onclick={handleClick}
onmouseenter={() => console.log('hover')}
>
Klick
</button>Das Event-Objekt steht als erstes Argument zur Verfügung, mit korrektem Type bei lang="ts".
Inline-Handler
<button onclick={() => count++}>
+1
</button>Inline-Handler sind in Svelte üblich, solange sie kurz bleiben.
Event-Modifier ersetzen
In Svelte 4 gab es Modifier wie |preventDefault, |once, |capture. In Svelte 5 schreibt man die Logik im Handler selbst:
<!-- Svelte 4 -->
<form on:submit|preventDefault={save}>...</form>
<!-- Svelte 5 -->
<form onsubmit={(e) => { e.preventDefault(); save(); }}>...</form><script>
let handled = $state(false);
function handle() {
if (handled) return;
handled = true;
// ...
}
</script>
<button onclick={handle}>Einmalig</button>Für capture-Listener gibt es das passende Suffix direkt im Attribut: onclickcapture={...}.
TypeScript für DOM-Events
Mit lang="ts" lassen sich Event-Typen ableiten:
<script lang="ts">
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement;
const formData = new FormData(form);
console.log(Object.fromEntries(formData));
}
</script>
<form onsubmit={handleSubmit}>
<input name="email" type="email" />
<button type="submit">Senden</button>
</form>Wer den Typ inline ableiten will, nutzt einen kurzen Cast: (e: Event) => { ... } oder die spezifischeren Sub-Typen wie MouseEvent, KeyboardEvent, SubmitEvent.
Component-Events als Callback-Props
createEventDispatcher ist Geschichte. Eigene Events einer Kind-Komponente werden als Funktionen (Callback-Props) modelliert:
<script>
let { onSave } = $props();
</script>
<button onclick={() => onSave?.({ timestamp: Date.now() })}>
Speichern
</button><SaveButton onSave={(detail) => console.log(detail.timestamp)} />Vorteile:
- Keine Dispatcher-Boilerplate mehr.
- Direkte Typisierbarkeit:
onSave: (detail: SaveDetail) => voidist eine normale Funktions-Signatur. - Konsistent mit DOM-Events: Der Aufrufer schreibt
onSave={...}— wie beionclick={...}. - Optional aufrufbar:
onSave?.()rendert die Komponente auch ohne Listener fehlerfrei.
Naming-Konvention für Component-Events
Die etablierte Konvention im React- und Svelte-5-Ökosystem: Callback-Props beginnen mit on und kommen in camelCase.
| Anlass | Prop-Name |
|---|---|
| Speichern-Aktion | onSave |
| Element selektieren | onSelect |
| Eintrag löschen | onDelete |
| Wert hat sich geändert | onChange |
| Modal schließen | onClose |
| Custom: Gestik abgeschlossen | onSwipeDone |
Damit wirken Komponenten-Events von außen identisch zu DOM-Events.
Event-Forwarding
Wenn deine Wrapper-Komponente einen DOM-Event direkt weiterreichen soll (z. B. ein Custom-Button, der den onclick des umschlossenen <button> exposed), nutzt du Spread:
<script>
let { children, ...rest } = $props();
</script>
<button class="styled" {...rest}>
{@render children()}
</button><StyledButton onclick={save} onmouseenter={hover}>
Speichern
</StyledButton>onclick und onmouseenter landen über den Spread direkt am inneren <button> — kein eigenes Forwarding nötig.
In Svelte 4 brauchte man dafür <button on:click on:mouseenter> (Forwarding-Syntax) — in Svelte 5 reicht der normale Spread.
TypeScript: Callback-Prop typisieren
<script lang="ts">
type Product = { id: string; name: string };
type Props = {
product: Product;
onAddToCart?: (product: Product) => void;
onRemove?: (id: string) => void;
};
let { product, onAddToCart, onRemove }: Props = $props();
</script>
<article>
<h3>{product.name}</h3>
<button onclick={() => onAddToCart?.(product)}>In den Warenkorb</button>
<button onclick={() => onRemove?.(product.id)}>Entfernen</button>
</article>Migration aus Svelte 4
Svelte 4 Svelte 5
------------------------------------------------ -----------------------------------------------------
on:click={handle} onclick={handle}
on:click|preventDefault={handle} onclick={(e) => { e.preventDefault(); handle(); }}
on:click|once={handle} manuell mit if (handled) return;-Flag
on:click|capture={handle} onclickcapture={handle}
on:click (Forwarding) {...rest} weiterreichen
createEventDispatcher + dispatch('save', x) Callback-Prop onSave={...}
<Component on:save={handle} /> <Component onSave={handle} />Häufige Stolperfallen
on:click in Svelte 5 verwendet.
Funktioniert im Legacy-Modus, in einer Runes-Komponente führt das zu einer Compiler-Warnung. Auf onclick={...} umstellen.
Callback-Prop mit dispatch-Mindset bauen.
<script>
let { onChange } = $props();
</script>
<input oninput={(e) => onChange({ detail: { value: e.target.value } })} /><input oninput={(e) => onChange?.(e.currentTarget.value)} />Das event.detail-Konstrukt aus Svelte 4 ist nicht mehr nötig — eine Callback-Prop kann direkt das Nutz-Datum übergeben.
Vergessen, dass Callback-Prop optional sein kann.
onSave?.(...) mit Optional-Chaining verhindert Fehler, falls die Eltern-Komponente keinen Listener anhängt.
bind:this mit Component-Events verwechseln.
Wer auf eine Funktion einer Kind-Komponente zugreifen will, nutzt bind:this und kann dann Methoden aufrufen — aber das ist kein Event. Siehe Component-Bindings.