> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/sveltejs/svelte/llms.txt
> Use this file to discover all available pages before exploring further.

# Event handlers

> Handle DOM events with on attributes in Svelte components

Listen to DOM events by adding attributes that start with `on` to elements. Svelte makes event handling simple and performant with automatic delegation for common events.

## Basic Event Handlers

Add event handlers using `on` followed by the event name:

```svelte theme={null}
<button onclick={() => console.log('clicked')}>click me</button>
```

## Event Attribute Syntax

Event attributes are **case sensitive**:

* `onclick` listens to the `click` event
* `onClick` listens to the `Click` event (different event)

This ensures you can listen to custom events with uppercase characters.

### Common Event Handlers

<CardGroup cols={2}>
  <Card title="Mouse Events">
    ```svelte theme={null}
    <div
      onclick={handleClick}
      ondblclick={handleDoubleClick}
      onmouseenter={handleMouseEnter}
      onmouseleave={handleMouseLeave}
      onmousemove={handleMouseMove}
    >
      Interactive area
    </div>
    ```
  </Card>

  <Card title="Keyboard Events">
    ```svelte theme={null}
    <input
      onkeydown={handleKeyDown}
      onkeyup={handleKeyUp}
      onkeypress={handleKeyPress}
      placeholder="Type here"
    />
    ```
  </Card>

  <Card title="Form Events">
    ```svelte theme={null}
    <form onsubmit={handleSubmit}>
      <input 
        oninput={handleInput}
        onchange={handleChange}
        onfocus={handleFocus}
        onblur={handleBlur}
      />
    </form>
    ```
  </Card>

  <Card title="Touch Events">
    ```svelte theme={null}
    <div
      ontouchstart={handleTouchStart}
      ontouchmove={handleTouchMove}
      ontouchend={handleTouchEnd}
    >
      Touch area
    </div>
    ```
  </Card>
</CardGroup>

## Event Object

Handlers receive the event object as the first parameter:

```svelte theme={null}
<script>
  function handleClick(event) {
    console.log('Button clicked at:', event.clientX, event.clientY);
    event.preventDefault();
  }
  
  function handleInput(event) {
    console.log('Current value:', event.target.value);
  }
</script>

<button onclick={handleClick}>Click me</button>
<input oninput={handleInput} />
```

## Inline Handlers

Write inline arrow functions for simple handlers:

```svelte theme={null}
<script>
  let count = $state(0);
</script>

<button onclick={() => count++}>
  Clicked {count} times
</button>
```

## Handler Shorthand

When the handler variable matches the event name, use shorthand:

```svelte theme={null}
<script>
  function onclick() {
    console.log('clicked');
  }
</script>

<!-- These are equivalent -->
<button {onclick}>click me</button>
<button onclick={onclick}>click me</button>
```

## Spreading Event Handlers

Event handlers can be spread like other attributes:

```svelte theme={null}
<script>
  const handlers = {
    onclick: () => console.log('clicked'),
    onmouseenter: () => console.log('mouse entered')
  };
</script>

<button {...handlers}>Interactive button</button>
```

## Event Timing

Event attributes fire **after** bindings update:

```svelte theme={null}
<script>
  let value = $state('');
  
  function handleInput(e) {
    // value is already updated from bind:value
    console.log('Input value:', value);
  }
</script>

<input bind:value oninput={handleInput} />
```

## Event Delegation

Svelte uses event delegation for better performance. A single listener at the application root handles these events:

* `beforeinput`
* `click`
* `change`
* `dblclick`
* `contextmenu`
* `focusin`
* `focusout`
* `input`
* `keydown`
* `keyup`
* `mousedown`
* `mousemove`
* `mouseout`
* `mouseover`
* `mouseup`
* `pointerdown`
* `pointermove`
* `pointerout`
* `pointerover`
* `pointerup`
* `touchend`
* `touchmove`
* `touchstart`

### Delegation Gotchas

<Warning>
  When using delegated events:

  * Set `{ bubbles: true }` when manually dispatching events
  * Avoid `stopPropagation()` or events won't reach the root
  * Handlers added with `addEventListener` inside the root run before declarative handlers
</Warning>

## Passive Touch Events

`ontouchstart` and `ontouchmove` handlers are [passive](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) for better scrolling performance:

```svelte theme={null}
<!-- These are passive by default -->
<div 
  ontouchstart={handleTouchStart}
  ontouchmove={handleTouchMove}
>
  Swipe here
</div>
```

To call `preventDefault()`, use the `on` function from `svelte/events`:

```svelte theme={null}
<script>
  import { on } from 'svelte/events';
  
  function setup(element) {
    on(element, 'touchstart', (event) => {
      event.preventDefault(); // This works
    });
  }
</script>

<div use:setup>Touch area</div>
```

## Real-World Examples

### Form Validation

```svelte theme={null}
<script>
  let email = $state('');
  let errors = $state([]);
  
  function validateEmail() {
    errors = [];
    if (!email) {
      errors.push('Email is required');
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      errors.push('Invalid email format');
    }
  }
  
  function handleSubmit(event) {
    event.preventDefault();
    validateEmail();
    
    if (errors.length === 0) {
      console.log('Form submitted:', email);
    }
  }
</script>

<form onsubmit={handleSubmit}>
  <input 
    bind:value={email}
    onblur={validateEmail}
    placeholder="Enter email"
  />
  
  {#if errors.length > 0}
    <ul class="errors">
      {#each errors as error}
        <li>{error}</li>
      {/each}
    </ul>
  {/if}
  
  <button type="submit">Submit</button>
</form>
```

### Keyboard Shortcuts

```svelte theme={null}
<script>
  let message = $state('');
  
  function handleKeydown(event) {
    // Ctrl/Cmd + S to save
    if ((event.ctrlKey || event.metaKey) && event.key === 's') {
      event.preventDefault();
      save();
    }
    
    // Escape to clear
    if (event.key === 'Escape') {
      message = '';
    }
  }
  
  function save() {
    console.log('Saving:', message);
  }
</script>

<textarea 
  bind:value={message}
  onkeydown={handleKeydown}
  placeholder="Type here (Ctrl+S to save, Esc to clear)"
></textarea>
```

### Click Outside

```svelte theme={null}
<script>
  let isOpen = $state(false);
  let dropdown;
  
  function handleClickOutside(event) {
    if (dropdown && !dropdown.contains(event.target)) {
      isOpen = false;
    }
  }
  
  $effect(() => {
    if (isOpen) {
      document.addEventListener('click', handleClickOutside);
      return () => {
        document.removeEventListener('click', handleClickOutside);
      };
    }
  });
</script>

<div bind:this={dropdown}>
  <button onclick={() => isOpen = !isOpen}>
    Toggle menu
  </button>
  
  {#if isOpen}
    <ul class="menu">
      <li>Option 1</li>
      <li>Option 2</li>
      <li>Option 3</li>
    </ul>
  {/if}
</div>
```

### Drag and Drop

```svelte theme={null}
<script>
  let isDragging = $state(false);
  let draggedItem = $state(null);
  
  function handleDragStart(event, item) {
    isDragging = true;
    draggedItem = item;
    event.dataTransfer.effectAllowed = 'move';
  }
  
  function handleDragOver(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }
  
  function handleDrop(event, targetItem) {
    event.preventDefault();
    isDragging = false;
    
    if (draggedItem !== targetItem) {
      console.log(`Dropped ${draggedItem.name} on ${targetItem.name}`);
    }
  }
</script>

{#each items as item (item.id)}
  <div
    draggable="true"
    ondragstart={(e) => handleDragStart(e, item)}
    ondragover={handleDragOver}
    ondrop={(e) => handleDrop(e, item)}
    class:dragging={isDragging && draggedItem === item}
  >
    {item.name}
  </div>
{/each}
```

### Debounced Search

```svelte theme={null}
<script>
  let searchTerm = $state('');
  let results = $state([]);
  let debounceTimer;
  
  function handleInput(event) {
    searchTerm = event.target.value;
    
    // Debounce search
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
      performSearch(searchTerm);
    }, 300);
  }
  
  async function performSearch(term) {
    if (!term) {
      results = [];
      return;
    }
    
    const res = await fetch(`/api/search?q=${term}`);
    results = await res.json();
  }
</script>

<input 
  type="search"
  oninput={handleInput}
  placeholder="Search..."
/>

{#if results.length > 0}
  <ul>
    {#each results as result}
      <li>{result.title}</li>
    {/each}
  </ul>
{/if}
```

## Best Practices

1. **Prevent default carefully** - Only call `preventDefault()` when necessary
2. **Clean up listeners** - Remove event listeners in `$effect` cleanup functions
3. **Use delegation** - Svelte's delegation optimizes common events automatically
4. **Avoid stopPropagation** - Can interfere with event delegation
5. **Debounce expensive operations** - Use timeouts for search, resize, scroll handlers
