Chapter 9: Vue v-on
v-on directive in Vue 3 (the one that makes your app actually do things when users interact with it). This is the directive for event handling, and in 2026 it’s still the backbone of interactivity in Vue.
v-on (shorthand: @) attaches event listeners to DOM elements or components. Think of it as Vue’s version of addEventListener(‘click’, …) — but declarative, reactive, and much cleaner.
Official description from vuejs.org (as of 2026):
v-on — Attach an event listener to the element. The event type is denoted by the argument. The expression can be a method name, an inline statement, or omitted if there are modifiers present.
Shorthand: @click instead of v-on:click
1. Basic Usage – Hello World Click Example
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<template> <div class="event-demo"> <p>Count: {{ count }}</p> <!-- Full syntax --> <button v-on:click="increment">+1 (full v-on)</button> <!-- Shorthand – 95% of real code uses this --> <button @click="increment">+1 (shorthand @)</button> <!-- Inline expression (simple cases) --> <button @click="count++">+1 inline</button> </div> </template> <script setup> import { ref } from 'vue' const count = ref(0) function increment() { count.value++ console.log('Button clicked! Count now:', count.value) } </script> <style scoped> .event-demo { text-align: center; padding: 2rem; } button { margin: 0.6rem; padding: 0.8rem 1.6rem; font-size: 1.1rem; cursor: pointer; } </style> |
→ Click any button → count updates reactively → No manual document.querySelector().addEventListener needed — Vue handles it.
2. Passing Arguments & Accessing the Native Event ($event)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<template> <button @click="greet('Rahul from Hyderabad')">Greet me</button> <!-- Access native DOM event with $event --> <button @click="logDetails($event, 'Extra info')">Log Details</button> </template> <script setup> function greet(name) { alert(`Namaste, ${name}! 🌶️`) } function logDetails(event, extra) { console.log('Event type:', event.type) // "click" console.log('Target tag:', event.target.tagName) // "BUTTON" console.log('Extra:', extra) console.log('Client X/Y:', event.clientX, event.clientY) } </script> |
- $event is a special Vue variable that gives you the real browser Event object
- You can mix arguments: first your params, last $event if needed
3. Event Modifiers – Vue’s Magic Shortcuts
These save you from writing event.preventDefault() or event.stopPropagation() manually.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<template> <!-- 1. Prevent default (no page reload on form submit) --> <form @submit.prevent="handleSubmit"> <input type="text" placeholder="Search..." /> <button type="submit">Go</button> </form> <!-- 2. Stop propagation (click on button doesn't bubble to parent div) --> <div @click="parentClicked" class="parent-box"> Parent div <button @click.stop="childClicked">Child button</button> </div> <!-- 3. Only once --> <button @click.once="oneTimeClick">Click me once only</button> <!-- 4. Self – only if clicked directly on this element --> <div @click.self="onlySelf">Click inside but not on children</div> <!-- 5. Passive – better scroll performance (no preventDefault possible) --> <div @wheel.passive="onWheel">Scroll me</div> </template> <script setup> function handleSubmit() { console.log('Form submitted!') } function parentClicked() { console.log('Parent div clicked') } function childClicked() { console.log('Child button – parent NOT triggered') } function oneTimeClick() { alert('This runs only the first time!') } function onlySelf() { console.log('Clicked directly on the div') } function onWheel() { console.log('Wheel event – passive') } </script> <style scoped> .parent-box { padding: 2rem; background: #e0f2fe; border: 2px dashed blue; } </style> |
Most common modifiers (must-know in 2026):
| Modifier | What it does | Common use case |
|---|---|---|
| .prevent | event.preventDefault() | Forms, links |
| .stop | event.stopPropagation() | Nested clickable elements |
| .once | Listener removed after first trigger | Analytics tracking, one-time actions |
| .self | Only if event.target === element | Avoid child clicks bubbling |
| .passive | Improves touch/scroll perf (can’t prevent) | Large scrollable areas |
| .capture | Use capture phase instead of bubble | Rare, advanced |
4. Key Modifiers – Keyboard Events Made Easy
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<template> <!-- Enter key --> <input @keyup.enter="search" placeholder="Type + Enter to search" /> <!-- Escape --> <input @keyup.esc="clearInput" placeholder="Esc to clear" /> <!-- Ctrl + Enter --> <textarea @keydown.ctrl.enter="submitMessage"></textarea> <!-- Exact combination --> <button @click.ctrl.exact="ctrlOnlyClick">Ctrl + Click only (no Alt/Shift)</button> </template> <script setup> function search() { console.log('Searching...') } function clearInput() { /* clear logic */ } function submitMessage() { console.log('Message sent!') } function ctrlOnlyClick() { console.log('Ctrl-only click detected') } </script> |
Key aliases: .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right
System modifiers: .ctrl, .shift, .alt, .meta (Windows key / Cmd)
.exact → no extra modifiers allowed
5. Dynamic Event Names (advanced but useful)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<template> <button @[eventName]="handleEvent">Dynamic event</button> </template> <script setup> import { ref } from 'vue' const eventName = ref('click') // can be 'mouseover', 'focus', etc. function handleEvent() { console.log(`Event: ${eventName.value}`) } </script> |
6. Object Syntax – Multiple Events at Once (less common)
|
0 1 2 3 4 5 6 7 8 |
<div v-on="{ mouseenter: showTooltip, mouseleave: hideTooltip, click: toggle }"> Hover or click me </div> |
Quick Reference Table – v-on in 2026
| Syntax Example | Meaning / Use Case |
|---|---|
| @click=”increment” | Basic click handler |
| @click=”say(‘Hi’, $event)” | With args + event object |
| @submit.prevent=”save” | Form submit without reload |
| @keyup.enter=”search” | Press Enter to trigger |
| @click.once | Run handler only once |
| @click.stop.prevent | Stop bubble + prevent default |
| @[dynamicEvent]=”handler” | Event name from variable |
| v-on=”{ click: fn1, focus: fn2 }” | Multiple events in object form |
Pro Tips from Real Projects (Hyderabad 2026 style)
- Prefer shorthand @ — it’s shorter and everyone uses it
- For complex logic → always use a method (not inline) → better debugging & reusability
- Custom components: @my-event=”handler” listens to emitted custom events (we covered in previous “Vue Events” lesson)
- Performance: .passive on scroll/touch → noticeable on mobile
- Avoid @click=”count++” in large apps — move to method for testability
Practice: Enhance your todo app — add:
- @click on delete button
- @submit.prevent on add form
- @keyup.enter on input to add todo
- @dblclick on todo to edit
Any confusion? Want full example with form + keyboard + modifiers? Or how v-on works on custom components vs native elements? Or TypeScript typing for events?
Just tell me — we’ll keep going step by step 🚀
Happy event-handling from Hyderabad! 💙
