Events and handler functions
An event is a signal from the browser: the user clicked a button, typed text, submitted a form. In Svelte 5 an event handler is a regular element attribute named on + the event name: onclick, oninput, onsubmit. You pass a function to it.
A click handler
<!-- src/routes/+page.svelte -->
<script>
let count = $state(0);
function handleClick() {
count++;
}
</script>
<button onclick={handleClick}>Clicked: {count}</button>Each click calls handleClick, and the number on the button grows. You pass the function itself — without parentheses. To pass an argument, use an arrow function: onclick={() => remove(todo.id)}.
The event object
The handler receives an event object — the same as in plain JavaScript. With a form it is used to cancel the page reload:
<!-- src/routes/+page.svelte -->
<script>
let todos = $state([]);
function addTodo(event) {
event.preventDefault();
const data = new FormData(event.currentTarget);
todos.push({ id: Date.now(), text: data.get('text'), done: false });
event.currentTarget.reset();
}
</script>
<form onsubmit={addTodo}>
<input name="text" placeholder="New task" />
<button>Add</button>
</form>
<p>Tasks: {todos.length}</p>Type some text and press Enter or the button — the counter grows and the field is cleared. Modifiers like |preventDefault from Svelte 4 no longer exist: everything is done inside the function.
A function as a prop
A child component does not change data it does not own; it tells the parent about the event. For this the parent passes it a function as a regular prop. By convention the name of such a prop also starts with on:
<!-- src/lib/components/TodoItem.svelte -->
<script>
let { todo, ontoggle, ondelete } = $props();
</script>
<li>
<input type="checkbox" checked={todo.done} onchange={() => ontoggle(todo.id)} />
{todo.text}
<button onclick={() => ondelete(todo.id)}>Delete</button>
</li>The component only calls the received functions and passes them the task's id.
Using it in the parent
<!-- src/routes/+page.svelte -->
<script>
import TodoItem from '$lib/components/TodoItem.svelte';
let todos = $state([
{ id: 1, text: 'Buy bread', done: false },
{ id: 2, text: 'Call Oleg', done: false }
]);
function toggle(id) {
const todo = todos.find((t) => t.id === id);
todo.done = !todo.done;
}
function remove(id) {
todos = todos.filter((t) => t.id !== id);
}
</script>
<ul>
{#each todos as todo (todo.id)}
<TodoItem {todo} ontoggle={toggle} ondelete={remove} />
{/each}
</ul>Clicking a checkbox changes the task's status in the parent's array, the "Delete" button removes the row. Data is stored and changed in one place — +page.svelte.
Common mistake
Calling the function instead of passing it:
<!-- src/lib/components/TodoItem.svelte: wrong -->
<button onclick={remove(todo.id)}>Delete</button>Here remove runs immediately when the markup is rendered and deletes the task, while onclick receives the result of the call. Wrap a call with arguments in an arrow function: onclick={() => remove(todo.id)}.