Interactive apps respond to what users do: clicks, key presses, form submissions, mouse movement. In Vue you listen to DOM events with the v-on directive -- almost always written with its shorthand @. This lesson covers inline handlers, method handlers, the event object, and Vue's powerful modifier system.
<script setup>
import { ref } from 'vue'
const count = ref(0)
function greet() {
alert('Hello from Vue!')
}
</script>
<template>
<button @click="count++">Clicked {{ count }} times</button>
<button @click="greet">Say hi</button>
</template>Two styles are shown here:
count++) run a small expression right in the template -- fine for one-liners.greet) reference a function, which is better for any real logic.When you pass a bare method name, Vue calls it with the native Event as the first argument. When you need both custom arguments and the event, use the special $event variable or an arrow function:
<template>
<input @input="onInput">
<button @click="remove(item.id, $event)">Delete</button>
<button @click="(e) => remove(item.id, e)">Delete (arrow)</button>
</template>
<script setup>
function onInput(event) {
console.log(event.target.value)
}
function remove(id, event) {
console.log('removing', id, 'via', event.type)
}
</script>Calling event.preventDefault() or event.stopPropagation() inside every handler is noisy. Vue lets you declare these concerns in the template with dot modifiers:
<form @submit.prevent="onSubmit">…</form> <!-- no page reload -->
<a @click.stop="select">…</a> <!-- stop bubbling -->
<div @click.self="close">…</div> <!-- only if target is the div itself -->
<button @click.once="init">…</button> <!-- fires a single time -->
<div @scroll.passive="onScroll">…</div> <!-- hints better scroll performance -->Modifiers can be chained: @click.stop.prevent="doThing". Order matters -- modifiers run left to right.
The @submit.prevent pattern is one of the most common in all of Vue: it keeps the browser from reloading the page so your JavaScript can handle the form instead.
For keyboard events you can filter by key without writing if statements:
<input @keyup.enter="submit">
<input @keyup.escape="cancel">
<input @keydown.ctrl.s.prevent="save"> <!-- Ctrl+S without the browser dialog -->Vue provides aliases for common keys: enter, tab, delete, esc, space, up, down, left, right. System modifier keys -- .ctrl, .alt, .shift, .meta -- combine with mouse and keyboard events. Add .exact when you want only those modifiers pressed: @click.ctrl.exact ignores Ctrl+Shift+Click.
Mouse buttons get modifiers too: .left, .right, .middle.
<script setup>
import { ref } from 'vue'
const query = ref('')
const results = ref([])
function search() {
if (!query.value) return
results.value = ['Result A for ' + query.value, 'Result B for ' + query.value]
}
function clear() {
query.value = ''
results.value = []
}
</script>
<template>
<form @submit.prevent="search">
<input v-model.trim="query" @keyup.escape="clear" placeholder="Search…">
<button type="submit">Go</button>
</form>
<ul>
<li v-for="r in results" :key="r">{{ r }}</li>
</ul>
</template>Submit with Enter or the button (no reload thanks to .prevent), and press Escape to clear. Notice how v-model and events cooperate naturally.
Because listeners live in the template next to the element, you can see behavior at a glance -- no hunting for addEventListener calls. Vue also removes all listeners automatically when the component unmounts, so there are no manual cleanup steps or memory leaks.
@event="handler" is the shorthand for v-on:event; use methods for real logic, inline expressions for trivial ones.$event gives you the native event when passing extra arguments..prevent, .stop, .once, and .self replace boilerplate calls.@keyup.enter, @keydown.ctrl.s) make keyboard handling declarative.Next up: Computed Properties and Watchers -- deriving state efficiently and reacting to changes.