Chapter 11: Vue Event Modifiers
Vue Event Modifiers — one of the nicest quality-of-life features Vue gives us. These little dot suffixes (.prevent, .stop, etc.) save you from writing boring boilerplate code like event.preventDefault() or event.stopPropagation() every single time.
In Vue 3 (still true in February 2026), event modifiers are special postfixes you add to the v-on directive (or its shorthand @) to change how the event is handled — without touching your JavaScript handler function.
Official Vue docs say:
It is a very common need to call event.preventDefault() or event.stopPropagation() inside event handlers. Although we can do this easily inside methods, it would be better if the methods can be purely about data logic rather than having to deal with DOM event details.
Modifiers let your methods stay clean — they focus on what to do, not how to tame the browser event.
Syntax Reminder
|
0 1 2 3 4 5 6 7 8 |
<button @click.prevent="doSomething">Click me</button> <!-- or full form --> <button v-on:click.prevent="doSomething">Click me</button> |
You can chain multiple modifiers:
|
0 1 2 3 4 5 6 |
<a @click.stop.prevent.self="handler">Link</a> |
Order usually doesn’t matter much (Vue applies them in a sensible internal order), but readability-wise people often put .prevent or .stop first.
All Built-in Event Modifiers in Vue 3 (2026)
Here’s the current official list from vuejs.org/guide/essentials/event-handling.html:
- .stop → Calls event.stopPropagation() Stops the event from bubbling up to parent elements.
- .prevent → Calls event.preventDefault() Prevents the browser’s default action (e.g. form submit reloads page, link navigates).
- .capture → Adds listener in capture phase instead of bubbling phase (Advanced: event goes top-down instead of bottom-up).
- .self → Only triggers if the event was dispatched directly on this element (not from a child).
- .once → Handler runs only once — listener is removed after first trigger.
- .passive → Marks listener as passive → improves scroll/touch performance (browser can optimize scrolling). Important: you cannot call preventDefault() inside a passive listener.
Mouse button modifiers (for click, mousedown, etc.):
- .left → Only left mouse button
- .right → Only right mouse button
- .middle → Only middle mouse button (wheel click)
Key modifiers (for keyup/keydown/keypress):
- .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right
- Or exact key code: .13 (Enter), .27 (Esc), etc.
- System modifiers: .ctrl, .shift, .alt, .meta (Cmd on Mac)
- .exact → no extra system modifiers allowed
Real-World Examples (Modern <script setup> Style)
1. .prevent – Most common – forms & links
|
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> <form @submit.prevent="handleSubmit"> <input type="text" v-model="searchQuery" placeholder="Search..." /> <button type="submit">Search</button> </form> <!-- Just prevent, no handler needed --> <a href="https://example.com" @click.prevent>Can't go there!</a> </template> <script setup> import { ref } from 'vue' const searchQuery = ref('') function handleSubmit() { console.log('Searching for:', searchQuery.value) // No page reload! } </script> |
2. .stop – Stop bubbling (nested clickables)
|
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 class="card" @click="cardClicked"> Card content <button @click.stop="buttonClicked"> Click me – won't trigger card click </button> </div> </template> <script setup> function cardClicked() { console.log('Card clicked!') } function buttonClicked() { console.log('Button clicked – card not triggered') } </script> <style scoped> .card { padding: 2rem; background: #e0f2fe; border: 2px dashed #3b82f6; border-radius: 8px; } </style> |
3. .self – Only direct clicks
|
0 1 2 3 4 5 6 7 8 9 |
<div @click.self="onlyDirect"> Outer <span>Inner text – clicking here does NOTHING</span> </div> |
4. .once – One-time actions
|
0 1 2 3 4 5 6 7 8 |
<button @click.once="trackAnalytics"> Track me only once! </button> |
|
0 1 2 3 4 5 6 7 8 9 |
function trackAnalytics() { console.log('Analytics sent – never again') // e.g. send to Google Analytics or PostHog only once } |
5. .passive – Smooth scrolling
|
0 1 2 3 4 5 6 7 8 |
<div class="scroll-container" @wheel.passive="onWheel"> Long scrollable content... </div> |
→ Browser can scroll smoothly without waiting to see if you call preventDefault()
6. Key + System Modifiers (powerful combo)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<input @keyup.enter="submitForm" placeholder="Type + Enter" /> <input @keyup.ctrl.enter.exact="saveDraft" placeholder="Ctrl + Enter to save draft (no Shift/Alt allowed)" /> <textarea @keydown.meta.s.prevent="saveWithCmdS" /> |
Chaining Modifiers (very common)
|
0 1 2 3 4 5 6 7 |
<form @submit.prevent.stop="handleSubmit"> <!-- prevent + stop bubble --></form> <a @click.stop.prevent.self="doAction">Link</a> |
Quick Cheat Sheet Table (print this!)
| Modifier | What it does | Most common use case | Example |
|---|---|---|---|
| .prevent | event.preventDefault() | Forms, links | @submit.prevent |
| .stop | event.stopPropagation() | Nested buttons/links | @click.stop |
| .once | Run only once | Analytics, tooltips, onboarding | @click.once |
| .self | Only if target === element | Avoid child triggers | @click.self |
| .capture | Capture phase (top → down) | Rare – global shortcuts | @keydown.capture.esc |
| .passive | Passive listener (perf) | Scroll, touch, wheel | @touchmove.passive |
| .left / .right / .middle | Mouse button filter | Context menus, drag | @mousedown.right |
| .enter / .esc etc. | Key filter | Forms, modals | @keyup.enter |
| .ctrl / .shift etc. | System key required | Shortcuts | @keydown.ctrl.s.prevent |
| .exact | No extra modifiers allowed | Precise shortcuts | @click.ctrl.exact |
Important 2026 Notes
- Modifiers only work on native DOM events (click, submit, keyup…). They do NOT work on custom component events (@my-event.stop has no effect — custom events don’t bubble by default in Vue 3).
- You can still access $event even with modifiers:
|
0 1 2 3 4 5 6 |
<button @click.prevent="handler($event)">Still get event</button> |
- Chaining too many modifiers? → Maybe extract to a method for clarity.
Practice challenge: Take a form component and:
- Prevent submit reload
- Stop propagation on inner buttons
- Add Enter key submit
- Make a “click once to confirm” button with .once
Any modifier confusing? Want a full shortcut-heavy app example (like VS Code style Cmd+S save)? Or how modifiers behave with custom events vs native?
Just shout — we’ll debug it together 🚀
Happy modifier-ing from Hyderabad! 🌶️💙
