Svelte 5 replaces on:
directives with direct property bindings for event handlers.
<script>
let count = $state(0);
function onclick() {
count++;
}
</script>
<button {onclick}>
clicks: {count}
</button>
Key changes:
- Removed
on:click
, replacing it with{onclick}
as a property. - Uses a named function (
onclick
) instead of inline arrow functions.
In Svelte 5, components no longer use createEventDispatcher()
. Instead, they accept callback props.
<script lang="ts">
let { inflate, deflate } = $props(); // Props passed from parent
let power = $state(5);
</script>
<button onclick={() => inflate(power)}>inflate</button>
<button onclick={() => deflate(power)}>deflate</button>
<button onclick={() => power--}>-</button>
Pump power: {power}
<button onclick={() => power++}>+</button>
<script lang="ts">
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function inflateHandler(power) {
size += power;
if (size > 75) burst = true;
}
function deflateHandler(power) {
if (size > 0) size -= power;
}
function reset() {
size = 15;
burst = false;
}
</script>
<Pump inflate={inflateHandler} deflate={deflateHandler} />
{#if burst}
<button onclick={reset}>new balloon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
Key changes:
- Removed
createEventDispatcher()
, instead using callback props (inflate
,deflate
). - Events are now just function calls inside
Pump.svelte
. - Parent (App.svelte) passes handlers like
inflateHandler
toPump
.
To forward an event from an element to a component, use callback props instead of on:
.
<script>
let { onclick } = $props(); // Accepts an `onclick` function from parent
</script>
<button {onclick}>
click me
</button>
Key changes:
- No need to manually forward events like
on:click
→{onclick}
does the job.
Instead of manually listing every event, just spread props.
<script>
let props = $props();
</script>
<button {...props}>
click me
</button>
Key changes:
{...props}
automatically applies all attributes, including event handlers likeonclick
.
Instead of duplicate event bindings, combine handlers manually.
<script>
function one(event) {
console.log('First handler', event);
}
function two(event) {
console.log('Second handler', event);
}
</script>
<button
onclick={(e) => {
one(e);
two(e);
}}
>
...
</button>
Key changes:
- Instead of multiple
on:click={one} on:click={two}
, handlers are manually merged.
Ensure that when spreading props, local event handlers still execute.
<script>
let props = $props();
function doStuff(event) {
console.log('Handled locally', event);
}
</script>
<button
{...props}
onclick={(e) => {
doStuff(e);
props.onclick?.(e);
}}
>
...
</button>
Key changes:
- The local
onclick
executes before theprops.onclick
, preventing overwrites.
Modifiers like once
and preventDefault
should now be implemented manually.
<script>
function once(fn) {
return function (event) {
if (fn) fn.call(this, event);
fn = null; // Ensures it only runs once
};
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
function handler(event) {
console.log('Button clicked');
}
</script>
<button onclick={once(preventDefault(handler))}>
...
</button>
Key changes:
- Used higher-order functions to replace
on:click|once|preventDefault={handler}
.
// svelte/events - on
function
// on
attaches an event handler and returns a cleanup function.
// It ensures correct handler order compared to addEventListener
.
import { on } from 'svelte/events';
// Attach an event to window
function on(
window: Window,
type: Type,
handler: (this: Window, event: WindowEventMap[Type]) => any,
options?: AddEventListenerOptions
): () => void;
// Attach an event to document
function on(
document: Document,
type: Type,
handler: (this: Document, event: DocumentEventMap[Type]) => any,
options?: AddEventListenerOptions
): () => void;
// Attach an event to an HTMLElement
function on<Element extends HTMLElement, Type extends keyof HTMLElementEventMap>(
element: Element,
type: Type,
handler: (this: Element, event: HTMLElementEventMap[Type]) => any,
options?: AddEventListenerOptions
): () => void;
// Attach an event to a MediaQueryList
function on<Element extends MediaQueryList, Type extends keyof MediaQueryListEventMap>(
element: Element,
type: Type,
handler: (this: Element, event: MediaQueryListEventMap[Type]) => any,
options?: AddEventListenerOptions
): () => void;
// Generic event attachment function on( element: EventTarget, type: string, handler: EventListener, options?: AddEventListenerOptions ): () => void;
Use on:eventname={handler}
to attach handlers (e.g., onclick={() => ...}
).
- Handlers can mutate
$state
directly; UI updates automatically. - Shorthand
{eventname}
works if the handler is a variable or function. - Events like
click
,input
, etc., are delegated; assume bubbling unless stopped. - Events fire after bindings (e.g.,
oninput
afterbind:value
). - Case-sensitive:
onclick
≠onClick
(custom events may use uppercase).
<button onclick={() => count++}> Increment: {count}
<button {logClick}> Log Click
<input oninput={(e) => text = e.target.value} bind:value={text} placeholder="Type here" />
You typed: {text}
<input onkeydown={(e) => e.key === 'Enter' && count++} placeholder="Press Enter to increment" />
Note: For other events (e.g., onmouseover
, onfocus
), follow the same pattern:
on:eventname={() => ...}
or {eventname}
. Adjust the handler logic based on the event’s purpose.