Chapter 8: Vue Events
Vue Events today — this is where your app starts feeling alive and interactive 😄
In Vue (especially Vue 3 in 2026), “events” means two closely related things:
- Listening to native DOM events (click, input, keyup, submit, mouseenter, etc.) on HTML elements → handled with v-on directive (shorthand @)
- Emitting and listening to custom events between components → child tells parent “something happened!” using $emit / emit → parent reacts with @custom-event
This follows the classic “Data Down, Events Up” pattern:
- Props send data down from parent → child
- Events send signals (and data) up from child → parent
We’ll cover both in detail, with modern Composition API + <script setup> examples (what you should use in 2026).
Part 1: Listening to Native DOM Events (v-on / @)
This is how you respond to user actions like clicks, typing, form submits.
Basic syntax:
|
0 1 2 3 4 5 6 7 8 |
<button v-on:click="handleClick">Click me</button> <!-- or shorthand (99% usage) --> <button @click="handleClick">Click me</button> |
Full real 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 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<!-- CounterButton.vue --> <template> <div class="counter"> <p>Count: {{ count }}</p> <button @click="increment">+1</button> <button @click="decrement">-1</button> <!-- With event object access --> <button @click="logEvent($event)">Log Event</button> <!-- Multiple handlers --> <button @click="increment; logClick()">Both!</button> </div> </template> <script setup> import { ref } from 'vue' const count = ref(0) function increment() { count.value++ } function decrement() { count.value-- } function logEvent(event) { console.log('Native event:', event.type, event.target.tagName) // event is the real DOM Event object } function logClick() { console.log('Someone clicked me!') } </script> <style scoped> .counter { text-align: center; padding: 2rem; } button { margin: 0.5rem; padding: 0.8rem 1.5rem; font-size: 1.1rem; } </style> |
Key points:
- Handler can be:
- A function name (increment)
- Inline statement (count++)
- Multiple with ; separator
- Automatic $event param → gives you the native DOM event
- You can pass arguments: @click=”sayHello(‘Rahul’)”
Event Modifiers (super useful shortcuts)
Vue gives modifiers to avoid 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 |
<template> <!-- Prevent form submit reload --> <form @submit.prevent="submitForm"> <input type="text" /> <button type="submit">Send</button> </form> <!-- Stop propagation (don't bubble up) --> <div @click="parentClick"> Parent <button @click.stop="childClick">Child Button</button> </div> <!-- Only once --> <button @click.once="oneTimeAlert">Click once only</button> <!-- Key modifiers (for keyboard events) --> <input @keyup.enter="search" placeholder="Press Enter to search" /> <input @keyup.esc="clear" placeholder="Press Esc to clear" /> </template> <script setup> function submitForm() { console.log('Form submitted!') } function parentClick() { console.log('Parent clicked') } function childClick() { console.log('Child clicked – parent not triggered') } function oneTimeAlert() { alert('This runs only once!') } function search() { console.log('Searching...') } function clear() { console.log('Cleared') } </script> |
Popular modifiers (2026 must-know):
- .prevent → event.preventDefault()
- .stop → event.stopPropagation()
- .once → listener removed after first trigger
- .self → only if event.target === element
- .passive → improves scroll performance
- Key: .enter, .tab, .delete, .esc, .space, .up, .ctrl, .shift, .alt, .meta
Part 2: Custom Component Events (Emitting & Listening)
This is how child components communicate up to parents.
Modern way in <script setup> (Vue 3.3+ best practice):
- Declare events with defineEmits
- Get emit function
- Call emit(‘eventName’, payload?)
Child example (InputWithValidation.vue):
|
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 41 42 |
<template> <div class="input-group"> <input type="text" :value="modelValue" @input="updateValue($event.target.value)" placeholder="Type something..." /> <small v-if="error" class="error">{{ error }}</small> </div> </template> <script setup> const props = defineProps({ modelValue: String, // for v-model support maxLength: { type: Number, default: 20 } }) const emit = defineEmits(['update:modelValue', 'valid-input', 'invalid-input']) const error = ref('') function updateValue(newVal) { if (newVal.length > props.maxLength) { error.value = `Max ${props.maxLength} characters!` emit('invalid-input', newVal) } else { error.value = '' emit('update:modelValue', newVal) // for v-model emit('valid-input', newVal) } } </script> <style scoped> .error { color: #dc2626; font-size: 0.9rem; } </style> |
Parent usage:
|
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 |
<template> <div> <h2>Custom Input Demo</h2> <InputWithValidation v-model="userInput" :max-length="15" @valid-input="onValid" @invalid-input="onInvalid" /> <p>Your input: {{ userInput }}</p> </div> </template> <script setup> import { ref } from 'vue' import InputWithValidation from './InputWithValidation.vue' const userInput = ref('') function onValid(value) { console.log('Valid input:', value) } function onInvalid(value) { console.log('Invalid (too long):', value) } </script> |
Key modern points:
- defineEmits([‘update:modelValue’, …]) → declares events (helps IDE, docs, avoids typos)
- For v-model support → emit ‘update:propName’
- Payload optional → emit(‘clicked’) or emit(‘clicked’, { id: 123, name: ‘Rahul’ })
- In older setup() (not <script setup>): const emit = defineEmits(…) still, but return { emit } if needed
Quick Summary Table – Vue Events 2026
| Type | Directive / Method | Example Syntax | Use Case |
|---|---|---|---|
| Native DOM | @event / v-on:event | @click=”increment” | Buttons, forms, keyboard, mouse |
| Native with arg | @event | @input=”val = $event.target.value” | Access event object |
| Modifier | .prevent, .stop, .enter | @submit.prevent=”save” | Forms, bubbling control, keys |
| Custom emit (child) | defineEmits + emit() | emit(‘update’, data) | Child → parent communication |
| Listen custom (parent) | @custom-event | @update=”handleUpdate” | React to child signals |
| v-model sugar | v-model + emit update:xxx | v-model=”value” | Two-way binding on custom components |
Practice this weekend:
- Build a todo item component that emits toggle-done and delete events
- Parent listens and updates list
- Add @keyup.enter on input to add todo
Any part unclear? Want full todo list with events example? Difference between native vs custom? Or how to type events with TypeScript (defineEmits<{ (e: ‘update’, val: string): void }>())?
Just tell me — we’ll debug it together over virtual chai ☕🚀
Keep building interactive UIs from Hyderabad! 💙
