Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the on:
directive to attach an event listener to an element, in Svelte 5 they are properties like any other:
<script>
let count = $state(0);
</script>
<button on:click={() => count++}>
<button onclick={() => count++}>
clicks: {count}
</button>
Since they're just properties, you can use the normal shorthand syntax...
<script>
let count = $state(0);
function onclick() {
count++;
}
</script>
<button {onclick}>
clicks: {count}
</button>
...though when using a named event handler function it's usually better to use a more descriptive name.
Traditional on:
event handlers will continue to work, but are deprecated in Svelte 5.
Component eventspermalink
In Svelte 4, components could emit events by creating a dispatcher with createEventDispatcher
.
This function is deprecated in Svelte 5. Instead, components should accept callback props (demo):
<script>
import Pump from './Pump.svelte';
let size = $state(15);
let burst = $state(false);
function reset() {
size = 15;
burst = false;
}
</script>
<Pump
inflate={() => {
size += 5;
if (size > 75) burst = true;
}}
deflate={() => {
if (size > 0) size -= 5;
}}
/>
{#if burst}
<button onclick={reset}>new balloon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
<script>
let { inflate, deflate } = $props();
</script>
<button onclick={inflate}>inflate</button>
<button onclick={deflate}>deflate</button>
Bubbling eventspermalink
Instead of doing <button on:click>
to 'forward' the event from the element to the component, the component should accept an onclick
callback prop:
<script>
let { onclick, children } = $props();
</script>
<button {onclick}>
{@render children()}
</button>
Note that this also means you can 'spread' event handlers onto the element along with other props:
<script>
let { children, ...props } = $props();
</script>
<button {...props}>
{@render children()}
</button>
Event modifierspermalink
In Svelte 4, you can add event modifiers to handlers:
<button on:click|once|preventDefault={handler}>...</button>
Modifiers are specific to on:
and as such do not work with modern event handlers. Adding things like event.preventDefault()
inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers.
Since event handlers are just functions, you can create your own wrappers as necessary:
<script>
function once(fn) {
return function (event) {
if (fn) fn.call(this, event);
fn = null;
};
}
function preventDefault(fn) {
return function (event) {
event.preventDefault();
fn.call(this, event);
};
}
</script>
<button onclick={once(preventDefault(handler))}>...</button>
There are three modifiers — capture
, passive
and nonpassive
— that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs.
For capture
, we add the modifier to the event name:
<button onclickcapture={...}>...</button>
Changing the passive
option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! - then you will need to use an action to apply the event handler yourself.
Multiple event handlerspermalink
In Svelte 4, this is possible:
<button on:click={one} on:click={two}>...</button>
This is something of an anti-pattern, since it impedes readability (if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other) and implies that the two handlers are independent, when in fact something like event.stopImmediatePropagation()
inside one
would prevent two
from being called.
Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this:
<button
onclick={(e) => {
one(e);
two(e);
}}
>
...
</button>
Why the change?permalink
By deprecating createEventDispatcher
and the on:
directive in favour of callback props and normal element properties, we:
- reduce Svelte's learning curve
- remove boilerplate, particularly around
createEventDispatcher
- remove the overhead of creating
CustomEvent
objects for events that may not even have listeners - add the ability to spread event handlers
- add the ability to know which event handlers were provided to a component
- add the ability to express whether a given event handler is required or optional
- increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event)