Skip to main content
Svelte Fundamentals
CHAPTER 07 Beginner

Event Handling in Svelte

Updated: May 18, 2026
5 min read

# CHAPTER 7

Event Handling in Svelte

1. Chapter Introduction

User interaction drives every application. A button click adds to cart. A keypress searches the database. A form submit saves data. Svelte's event handling is clean, intuitive, and powerful — using the on:eventname directive to wire DOM events directly to JavaScript functions. Event modifiers like |preventDefault and |once eliminate common boilerplate.

2. Learning Objectives

  • Use on:click, on:input, on:keydown, on:submit.
  • Apply event modifiers (|preventDefault, |stopPropagation, |once).
  • Access the native event object with $event.
  • Create and dispatch custom events between components.
  • Build a complete interactive Todo App.

3. Basic Event Handling

svelte
12345678910111213141516171819202122
<script>
  let clickCount = 0;
  let message = &#039;';

  function handleClick() {
    clickCount++;
  }

  function handleInput(event) {
    message = event.target.value;
  }
</script>

<!-- Click event -->
<button on:click={handleClick}>Clicked {clickCount} times</button>

<!-- Inline click with arrow function -->
<button on:click={() => clickCount = 0}>Reset</button>

<!-- Input event -->
<input on:input={handleInput} placeholder="Type something..." />
<p>You typed: {message}</p>

4. Event Modifiers

Modifiers save you from writing event.preventDefault() etc. manually:
svelte
1234567891011121314
<!-- Prevent default form submission (no page reload) -->
<form on:submit|preventDefault={handleSubmit}>

<!-- Stop event from bubbling up to parent elements -->
<div on:click|stopPropagation={() => console.log(&#039;div clicked')}>

<!-- Only fire once, then remove the listener -->
<button on:click|once={handleOneTimeSetup}>Initialize</button>

<!-- Combine multiple modifiers -->
<form on:submit|preventDefault|stopPropagation={handleSubmit}>

<!-- Passive — improves scroll performance (cannot prevent default) -->
<div on:scroll|passive={handleScroll}>

5. Accessing the Event Object

svelte
1234567891011121314151617
<script>
  function handleKeydown(event) {
    if (event.key === &#039;Enter') {
      console.log(&#039;Enter pressed!');
    }
    if (event.key === &#039;Escape') {
      closeModal();
    }
  }

  function handleMouseMove(event) {
    console.log(`Mouse at: ${event.clientX}, ${event.clientY}`);
  }
</script>

<input on:keydown={handleKeydown} placeholder="Press Enter or Escape" />
<div on:mousemove={handleMouseMove} class="canvas">Move mouse here</div>

6. Custom Events with createEventDispatcher

Child components communicate with parents by dispatching custom events:
svelte
12345678910111213141516171819202122
<!-- ConfirmDialog.svelte -->
<script>
  import { createEventDispatcher } from &#039;svelte';

  export let message = &#039;Are you sure?';

  const dispatch = createEventDispatcher();

  function confirm() {
    dispatch(&#039;confirm', { confirmed: true, timestamp: Date.now() });
  }

  function cancel() {
    dispatch(&#039;cancel');
  }
</script>

<div class="dialog">
  <p>{message}</p>
  <button on:click={cancel}>Cancel</button>
  <button class="danger" on:click={confirm}>Confirm</button>
</div>
svelte
12345678910111213141516171819202122
<!-- Parent component using ConfirmDialog -->
<script>
  import ConfirmDialog from &#039;./ConfirmDialog.svelte';
  let showDialog = false;
  let lastAction = &#039;';

  function handleConfirm(event) {
    lastAction = `Confirmed at ${new Date(event.detail.timestamp).toLocaleTimeString()}`;
    showDialog = false;
  }
</script>

{#if showDialog}
  <ConfirmDialog
    message="Delete this item?"
    on:confirm={handleConfirm}
    on:cancel={() => showDialog = false}
  />
{/if}

<button on:click={() => showDialog = true}>Delete Item</button>
<p>{lastAction}</p>

7. Mini Project: Interactive Todo App

svelte
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
<!-- TodoApp.svelte -->
<script>
  let newTodoText = &#039;';
  let todos = [
    { id: 1, text: &#039;Learn Svelte reactivity', done: true },
    { id: 2, text: &#039;Build a todo app', done: false },
    { id: 3, text: &#039;Deploy to production', done: false }
  ];
  let nextId = 4;
  let filter = &#039;all'; // all, active, done

  $: filteredTodos = filter === &#039;all' ? todos :
    filter === &#039;active' ? todos.filter(t => !t.done) :
    todos.filter(t => t.done);

  $: remainingCount = todos.filter(t => !t.done).length;

  function addTodo() {
    if (newTodoText.trim()) {
      todos = [...todos, { id: nextId++, text: newTodoText.trim(), done: false }];
      newTodoText = &#039;';
    }
  }

  function handleKeydown(e) {
    if (e.key === &#039;Enter') addTodo();
  }

  function toggle(id) {
    todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
  }

  function remove(id) {
    todos = todos.filter(t => t.id !== id);
  }

  function clearCompleted() {
    todos = todos.filter(t => !t.done);
  }
</script>

<div class="app">
  <h1>✅ Todo App</h1>

  <div class="input-row">
    <input
      bind:value={newTodoText}
      on:keydown={handleKeydown}
      placeholder="What needs to be done?"
    />
    <button on:click={addTodo}>Add</button>
  </div>

  <div class="filters">
    {#each ['all', 'active', 'done'] as f}
      <button class:selected={filter === f} on:click={() => filter = f}>
        {f.charAt(0).toUpperCase() + f.slice(1)}
      </button>
    {/each}
  </div>

  <ul>
    {#each filteredTodos as todo (todo.id)}
      <li class:done={todo.done}>
        <input type="checkbox" checked={todo.done} on:change={() => toggle(todo.id)} />
        <span>{todo.text}</span>
        <button on:click={() => remove(todo.id)}>✕</button>
      </li>
    {/each}
  </ul>

  <div class="footer">
    <span>{remainingCount} item{remainingCount !== 1 ? &#039;s' : ''} left</span>
    <button on:click={clearCompleted}>Clear completed</button>
  </div>
</div>

8. Common Mistakes

  • Calling functions immediately: on:click={doSomething()} calls doSomething at render time and assigns the return value as the handler. Use on:click={doSomething} (no parentheses).
  • Forgetting |preventDefault on forms: Without it, submitting a form causes a page reload, destroying all Svelte state.

9. MCQs with Answers

Question 1

What is the Svelte directive for handling click events?

Question 2

What modifier prevents the default browser behavior (like form submission)?

Question 3

What modifier ensures an event listener fires only once?

Question 4

When passing a handler with parameters, what is the correct syntax?

Question 5

What Svelte function enables custom event dispatching from child components?

Question 6

When a child dispatches dispatch('confirm', { data: 1 }), how does the parent access the data?

Question 7

What modifier prevents an event from bubbling to parent elements?

Question 8

What modifier improves scroll performance by telling the browser the handler won't call preventDefault?

Question 9

What is wrong with on:click={doSomething()}?

Question 10

How do you listen for keyboard events on an input?

10. Interview Questions

  • Q: How does Svelte's event modifier system compare to calling event.preventDefault() manually?
  • Q: Explain the createEventDispatcher pattern. How is it similar to @Output in Angular?

11. Summary

Svelte's event handling is clean and declarative. The on:eventname directive keeps event wiring close to the HTML it affects. Event modifiers eliminate common boilerplate. And createEventDispatcher provides a clean, type-safe way for child components to communicate upward. Together, these tools handle every user interaction scenario in a few expressive lines.

12. Next Chapter Recommendation

In Chapter 8: Conditional Rendering and Loops, we control what the user sees using {#if}, {:else}, and {#each} blocks to build a dynamic, data-driven product listing.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·