Vue 3: gestionar eventos con v-on (@click, $event y modificadores)
verificado el 7 septiembre 2026 · 6 min
v-on:click, o su atajo @click, ejecuta código cuando se produce el evento. Pasa el nombre de una función para recibir el evento nativo, o llámala con sus argumentos y $event si necesitas ambos. Los modificadores .prevent, .stop, .once, .enter o .esc sustituyen el código repetitivo de gestión de eventos.
Por ahora, la aplicación muestra cosas. No reacciona a nada. La directiva v-on asocia un listener de eventos a un elemento y ejecuta código cuando el evento se produce. Este capítulo cubre todo lo que se hace con ella a diario: un clic, el paso del ratón, una tecla, con o sin argumento, y los modificadores que evitan escribir preventDefault() a mano.
El carrito
Añade un contador y un botón:
<script setup>
import { ref } from 'vue'
const product = ref('T-Shirt')
const image = ref('/images/t-shirt-bleu.svg')
const inventory = ref(100)
const cart = ref(0)
const details = ref(['60 % coton', '30 % laine', '10 % polyester'])
const variants = ref([
{ id: 2234, color: 'Bleu', image: '/images/t-shirt-bleu.svg' },
{ id: 2235, color: 'Rouge', image: '/images/t-shirt-rouge.svg' }
])
</script>
<template>
<div class="product-display">
<div class="cart">Panier ({{ cart }})</div>
…
<button class="button">Ajouter au panier</button>
</div>
</template>Escuchar un clic
v-on: seguido del nombre del evento, con el código que hay que ejecutar entre comillas:
<button class="button" v-on:click="cart += 1">Ajouter au panier</button>Haz clic: el contador sube. El nombre del evento es el del DOM estándar, sin prefijo: click, submit, input, keyup, mouseover.
Llamar a una función
Una expresión en línea sirve para una operación trivial. En cuanto la lógica ocupa más de una línea, escribe una función:
<script setup>
function addToCart() {
cart.value += 1
}
</script>
<template>
<button class="button" v-on:click="addToCart">Ajouter au panier</button>
</template>Fíjate en el .value del script: manipulamos la referencia, no el valor desenvuelto. En la plantilla se escribe siempre {{ cart }}, sin .value.
@click="addToCart" pasa la función en sí: Vue la llama con el evento nativo como primer argumento. @click="addToCart()" la llama sin argumentos. Aquí funcionan las dos. El error clásico aparece cuando la función espera un valor: @click="updateImage(variant.image)" es correcto, pero olvidar los paréntesis pasaría el evento en lugar de la ruta de la imagen.
El atajo con arroba
Igual que v-bind tiene :, v-on tiene @:
<button class="button" @click="addToCart">Ajouter au panier</button>Es la forma que verás en toda la documentación y en el resto de este tutorial.
Pasar un argumento
Hagamos que la imagen cambie al pasar el ratón por un color. La función recibe la ruta de la imagen de la variante sobre la que está el ratón:
<script setup>
function updateImage(variantImage) {
image.value = variantImage
}
</script>
<template>
<div
v-for="variant in variants"
:key="variant.id"
@mouseover="updateImage(variant.image)"
>
{{ variant.color }}
</div>
</template>Pasa el ratón por «Rouge»: la imagen se vuelve roja. Aquí se combinan dos mecanismos: el bucle del capítulo 4 y el enlace de atributos del capítulo 2.
Recuperar el evento nativo
Sin argumentos, la función recibe el evento del DOM. Con argumentos, pídelo explícitamente con $event:
<button @click="voir">Sans argument</button>
<button @click="voirAvec('bleu', $event)">Avec arguments</button>function voir(event) {
console.log(event.target)
}
function voirAvec(couleur, event) {
console.log(couleur, event.target)
}El objeto recibido es el evento nativo del navegador, sin envoltorio: event.target, event.key y event.preventDefault() funcionan igual que en JavaScript puro.
Los modificadores de evento
Vue añade sufijos que evitan el código repetitivo de gestión de eventos:
<form @submit.prevent="envoyer">…</form> <!-- event.preventDefault() -->
<div @click.stop="…">…</div> <!-- event.stopPropagation() -->
<div @click.self="…">…</div> <!-- seulement si la cible est cet élément -->
<button @click.once="…">…</button> <!-- une seule fois -->
<input @keyup.enter="valider"> <!-- seulement la touche Entrée -->La lista completa, con lo que sustituye cada modificador:
| Modificador | Equivalente en JavaScript | Uso típico |
|---|---|---|
.prevent | event.preventDefault() | un formulario que no debe recargar la página |
.stop | event.stopPropagation() | un botón dentro de una tarjeta clicable: el clic no sube hasta la tarjeta |
.self | if (event.target !== event.currentTarget) return | cerrar una ventana modal al hacer clic en el fondo, no en su contenido |
.once | addEventListener(…, { once: true }) | un botón «Realizar pedido» que no debe enviar dos veces |
.capture | addEventListener(…, { capture: true }) | interceptar el evento antes que los hijos |
.passive | addEventListener(…, { passive: true }) | scroll y toque en móvil, para no bloquear el renderizado |
Los modificadores se encadenan y se leen de izquierda a derecha: @click.stop.prevent detiene la propagación y después anula el comportamiento por defecto. Este orden importa con .self: @click.self.prevent solo anula el comportamiento por defecto de los clics sobre el propio elemento; @click.prevent.self lo anula para todos los clics, hijos incluidos.
@submit.prevent servirá en el capítulo 10 para impedir que la página se recargue al enviar el formulario.
Modificadores de tecla y de ratón
En los eventos de teclado, un modificador filtra la tecla. Vue acepta cualquier nombre de tecla expuesto por KeyboardEvent.key, escrito en kebab-case, más algunos alias:
<input @keyup.enter="valider">
<input @keyup.esc="annuler">
<input @keydown.tab="suivant">
<input @keyup.page-down="pageSuivante"> <!-- KeyboardEvent.key === 'PageDown' -->
<input @keydown.ctrl.enter="envoyer"> <!-- Ctrl + Entrée -->Los alias disponibles: .enter, .tab, .delete (Supr y Retroceso), .esc, .space, .up, .down, .left, .right. Las teclas de sistema .ctrl, .alt, .shift y .meta se combinan con las demás. Y .exact exige que no haya ninguna otra tecla de sistema pulsada:
<button @click.ctrl="ouvrir">…</button> <!-- Ctrl + clic, même si Shift est aussi enfoncé -->
<button @click.ctrl.exact="ouvrir">…</button> <!-- Ctrl + clic, et rien d'autre -->
<button @click.exact="ouvrir">…</button> <!-- clic sans aucune touche système -->Para el ratón, .left, .right y .middle filtran el botón: @click.right.prevent="menuContextuel" sustituye el menú del navegador por el tuyo.
Varios gestores en un mismo evento
Un evento puede disparar varias funciones, separadas por comas. En ese caso, cada función se llama con paréntesis:
<button @click="addToCart(), trackClick('ajout-panier')">Ajouter au panier</button>Más allá de dos llamadas deja de ser legible: escribe una función que llame a las demás.
Resumen
v-on:clicko@click: una expresión en línea para una operación trivial; si no, una función.- Sin paréntesis, la función recibe el evento nativo; con argumentos,
$eventlo pasa además. .prevent,.stop,.selfy.oncesustituyen el código repetitivo; los modificadores de tecla filtran el teclado;.exactfija las combinaciones.- En el script, el estado se modifica con
.value; en la plantilla, sin él.
Las funciones viven en la opción methods y acceden al estado mediante this: methods: { addToCart() { this.cart += 1 } }. La plantilla, en cambio, no cambia.
Los eventos de este capítulo vienen del DOM. Para que un componente hijo avise a su padre, Vue ofrece eventos personalizados con emit: es el tema del capítulo 9, detallado en la guía completa de Vue emit.
Errores frecuentes
cart += 1 no hace nada útil sobre una referencia: escribe cart.value += 1.@click="updateImage" pasa el evento nativo en lugar del valor esperado. Con un argumento, llama a la función: @click="updateImage(variant.image)".$event: @click="faire('bleu', $event)".@click.prevent.self impide el comportamiento por defecto de todos los clics; @click.self.prevent, solo el de aquellos cuyo destino es el propio elemento. El orden se lee de izquierda a derecha..passive promete al navegador que no se llamará a preventDefault(); combinado con .prevent, el navegador ignora uno de los dos y muestra un aviso.