07 - Event Handlers
What Changed?
Svelte 5 uses standard DOM event attributes (onclick, oninput, etc.) instead of Svelte 4's custom directive syntax (on:click, on:input). Event handlers are now just props — no special syntax needed.
Svelte 4 (Legacy)
<button on:click={handleClick}>Click</button>
<button on:click|preventDefault={handleSubmit}>Submit</button>Svelte 5
<button onclick={handleClick}>Click</button>
<!-- No more modifiers like |preventDefault — handle it in the function -->Basic Usage
<!-- src/lib/components/Counter.svelte -->
<script>
let count = $state(0);
function handleClick() {
count++;
}
</script>
<button onclick={handleClick}>Count: {count}</button>Inline Handlers
For simple logic, you can write the handler directly inline:
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>Count: {count}</button>Event Object
The handler receives the native DOM event object as its first argument:
<script>
function handleClick(event) {
console.log(event.target); // the <button> element
}
</script>
<button onclick={handleClick}>Click</button>Since there are no more modifiers like on:click|preventDefault, you handle it yourself:
<script>
function handleSubmit(event) {
event.preventDefault();
// form logic here
}
</script>
<form onsubmit={handleSubmit}>
<button>Submit</button>
</form>Custom Events in Child Components
In Svelte 4, you used createEventDispatcher. In Svelte 5, custom events are just callback props — pass a function from parent to child.
Child:
<!-- src/lib/components/SearchForm.svelte -->
<script>
let { onsubmit } = $props(); // callback prop from parent
let value = $state('');
</script>
<form onsubmit={(e) => {
e.preventDefault();
onsubmit?.(value); // call the parent's function
}}>
<input bind:value />
<button>Submit</button>
</form>Parent:
<!-- src/routes/+page.svelte -->
<script>
import SearchForm from '$lib/components/SearchForm.svelte';
function handleSubmit(value) {
console.log('Submitted:', value);
}
</script>
<SearchForm onsubmit={handleSubmit} />The ?. in onsubmit?.(value) is optional chaining — it safely does nothing if the parent didn't pass an onsubmit prop.
Passing Additional Arguments
onclick={handleClick} only passes the native event object. Wrap in an arrow function to pass extra arguments:
<script>
function handleClick(message, num, event) {
console.log(message, num, event.target);
}
</script>
<button onclick={(event) => handleClick('hello', 42, event)}>Click</button>Especially useful in loops:
{#each items as item, index}
<button onclick={() => handleClick(item, index)}>
{item.name}
</button>
{/each}Multiple Handlers
No more on:click chaining. Wrap multiple calls in a single handler:
<script>
function handler1() { console.log('Handler 1'); }
function handler2() { console.log('Handler 2'); }
</script>
<button onclick={(e) => {
handler1();
handler2();
}}>
Click
</button>Key Points
- Use
onclicknoton:click— standard DOM attribute names - Event handlers are just props, no special Svelte syntax
- No more modifiers (
|preventDefault,|stopPropagation) — callevent.preventDefault()etc. in the handler - No more
createEventDispatcher— pass callback props instead - Use optional chaining
onsubmit?.()for optional callback props
Interactive Example
<script>
import Child from './07-events-child.svelte';
let count = $state(0);
let clickLog = $state([]);
let submissions = $state([]);
function handleClick(event) {
count++;
clickLog.push(`Clicked at ${new Date().toLocaleTimeString()}`);
if (clickLog.length > 5) clickLog.shift();
}
function handleSubmit(value) {
submissions.push(value);
}
</script>
<div>
<h2>07 - Event Handlers Example</h2>
<div>
<h3>Basic Events (onclick)</h3>
<button onclick={handleClick}>
Click me! (Count: {count})
</button>
<div>
{#each clickLog as log}
<div>{log}</div>
{/each}
</div>
</div>
<div>
<h3>Inline Handlers</h3>
<button onclick={() => count++}>Increment</button>
<button onclick={() => count--}>Decrement</button>
<button onclick={() => count = 0}>Reset</button>
<p>Count: {count}</p>
</div>
<div>
<h3>Custom Events (via callback props)</h3>
<Child onsubmit={handleSubmit} />
<div>
<strong>Submissions:</strong>
{#each submissions as sub}
<div>{sub}</div>
{/each}
</div>
</div>
</div>
<style>
div { margin: 20px; }
h3 { margin-top: 20px; }
button { margin: 5px; }
</style>