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.
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:
<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
Mouse Events
<div
onclick={handleClick}
ondblclick={handleDoubleClick}
onmouseenter={handleMouseEnter}
onmouseleave={handleMouseLeave}
onmousemove={handleMouseMove}
>
Interactive area
</div>
Keyboard Events
<input
onkeydown={handleKeyDown}
onkeyup={handleKeyUp}
onkeypress={handleKeyPress}
placeholder="Type here"
/>
Form Events
<form onsubmit={handleSubmit}>
<input
oninput={handleInput}
onchange={handleChange}
onfocus={handleFocus}
onblur={handleBlur}
/>
</form>
Touch Events
<div
ontouchstart={handleTouchStart}
ontouchmove={handleTouchMove}
ontouchend={handleTouchEnd}
>
Touch area
</div>
Event Object
Handlers receive the event object as the first parameter:
<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:
<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:
<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:
<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:
<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
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
Passive Touch Events
ontouchstart and ontouchmove handlers are passive for better scrolling performance:
<!-- These are passive by default -->
<div
ontouchstart={handleTouchStart}
ontouchmove={handleTouchMove}
>
Swipe here
</div>
To call preventDefault(), use the on function from svelte/events:
<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
<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
<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
<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
<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
<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
- Prevent default carefully - Only call
preventDefault() when necessary
- Clean up listeners - Remove event listeners in
$effect cleanup functions
- Use delegation - Svelte’s delegation optimizes common events automatically
- Avoid stopPropagation - Can interfere with event delegation
- Debounce expensive operations - Use timeouts for search, resize, scroll handlers