Event Handling with v-on
v-on, almost always written with its @ shorthand, is Vue's directive for listening to DOM events and, on components, custom emitted events. It accepts either an inline JavaScript expression evaluated in the template's scope, or the name of a component method to call as a handler, receiving the native or custom event object as its first argument automatically when needed. Because v-on hooks directly into the underlying addEventListener mechanics, it supports the full range of native DOM events (click, input, submit, keydown, and so on) as well as any custom event name a child component emits via defineEmits.
Cricket analogy: v-on is like an umpire's earpiece that listens for both standard signals — a bowler's appeal, a batsman's leave — and custom team signals a fielder invents, hooking directly into the same underlying communication channel either way.
Inline expressions vs method handlers
For very short logic, an inline expression like @click="count++" is idiomatic and avoids the ceremony of defining a separate function. For anything beyond a one-liner, Vue expects a method handler — a function reference like @click="handleSubmit" — which Vue automatically passes the native event object to as the first parameter. If you need to pass the event alongside custom arguments, you call the function explicitly in the template and reference the special $event variable, e.g. @click="handleSubmit($event, item.id)", so the native event isn't lost when you add extra arguments.
Cricket analogy: An inline handler like @click="runs++" is idiomatic for a quick scoreboard tap, but for anything bigger you use a named method like @click="recordWicket", which Vue automatically passes the event to; to also pass the batsman's id you call it explicitly with $event.
Event modifiers
Vue provides event modifiers — dot-suffixed keywords appended to the event name — that handle common patterns declaratively instead of doing them imperatively inside the handler. .stop calls event.stopPropagation(), .prevent calls event.preventDefault(), .self only triggers the handler if the event originated on the element itself (not a child), .once removes the listener after it fires once, and .capture uses the capture phase instead of bubbling. Modifiers can be chained, and their order matters — @click.prevent.stop prevents default first, then stops propagation, applied in the order written. There are also key modifiers for keyboard events, like @keyup.enter or @keydown.esc, which filter the handler to only run for that specific key.
Cricket analogy: Event modifiers are like an umpire's shorthand signals: .stop is waving off a runner before the throw goes further, .prevent is calling dead ball before play continues, .once is a one-time no-ball warning, and .self only counts an appeal from the bowler himself, not a teammate.
<script setup>
import { ref } from 'vue'
const email = ref('')
const submitted = ref(false)
function handleSubmit(event) {
submitted.value = true
console.log('Form submitted with', email.value)
}
function removeItem(event, itemId) {
console.log('Removing item', itemId, 'triggered by', event.type)
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<input v-model="email" type="email" @keyup.enter="handleSubmit" />
<button type="submit">Submit</button>
</form>
<button @click="removeItem($event, 42)">Remove item 42</button>
</template>Vue's event modifiers exist because raw event-handling boilerplate (event.preventDefault(), event.stopPropagation()) is extremely common and easy to forget. React has no template-level equivalent — you always call event.preventDefault() manually inside the handler function. Vue's declarative modifiers move that intent into the template itself, making the handler's purpose easier to scan at a glance.
On a custom component, @click="handler" does NOT automatically listen to the child's root element's native click event unless the child explicitly forwards it — actually, by default Vue does pass non-emitted native listeners through to the component's root element via 'attribute fallthrough', but if the child component declares its own click in defineEmits, or has multiple root nodes, that fallthrough behavior can be disabled or become ambiguous. Always check whether the event you're binding on a custom component is a native DOM event falling through, or a custom event explicitly emitted via defineEmits — they resolve differently and mixing up the two is a common source of 'my click handler never fires' bugs.
- v-on (shorthand @) binds both native DOM events and custom component-emitted events to handler expressions or methods.
- Method handlers automatically receive the native event object as the first argument; use $event to pass it explicitly alongside custom arguments.
- Modifiers like .stop, .prevent, .self, .once, and .capture replace common imperative event-object calls with declarative template syntax.
- Modifier chaining order matters — modifiers are applied in the order they're written.
- Key modifiers (e.g. .enter, .esc) restrict a keyboard handler to fire only for a specific key.
- On custom components, native listeners fall through to the root element by default, but this can be affected by multiple root nodes or explicit emits declarations.
Practice what you learned
1. What does the `.prevent` modifier do when attached to an event listener?
2. When a method handler is bound with `@click="handleClick"` (no explicit call), what does Vue pass to handleClick?
3. How do you pass a custom argument alongside the native event object to a handler?
4. Which modifier restricts a keyboard event handler to only fire when the Enter key is pressed?
5. What does the `.self` modifier guarantee about when the handler fires?
Was this page helpful?
You May Also Like
Form Input Bindings with v-model
Details how v-model implements two-way binding on form elements and components, what it compiles down to, and its modifiers and pitfalls.
Template Syntax
A tour of Vue's HTML-based template syntax, including text interpolation, directive bindings, and the shorthand notations for v-bind and v-on.
Emitting Custom Events
Learn how child components communicate upward to parents using Vue's custom event system, including typed emits and the defineEmits macro.
Conditional Rendering: v-if and v-show
Explains Vue's two conditional rendering directives, how they differ at the DOM level, and how to choose the right one for performance and correctness.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics