Vue – Events and Emits

November 8, 20253 min readUpdated 8/24/2026

Props go down; events come up. A child never reaches into its parent — it announces that something happened and lets the parent decide what that means.

defineEmits

const emit = defineEmits(["like", "comment", "share", "view", "toggle-mute"]);

Like defineProps, this is a compiler macro — no import, top level only. It returns the function you call to emit:

function remove(tag) {
  emit("update:modelValue", props.modelValue.filter((t) => t !== tag));
}

The parent listens with @:

<ReelPlayer
  :reel="reel"
  :active="index === activeIndex"
  @like="onLike(reel)"
  @view="onView"
  @share="onShare(reel)"
/>

Note the two forms. @view="onView" passes the handler, so it receives the emitted payload as its argument. @like="onLike(reel)" is an inline expression, so it calls onLike with the reel instead — and the payload is available as $event if you need it too.

Declaring events is optional but worth it

emit("like") works whether or not you declared it. Declaring is worth the line for three reasons: the component's interface is documented in one place, tooling can check it, and a declared event is removed from $attrs — so it does not also get bound as a native DOM listener on the root element, which causes a genuinely puzzling double-fire.

Event modifiers

The small pieces of glue that every form needs. Instead of calling event.preventDefault() in the handler, say it in the template:

<form @submit.prevent="submit">

The useful ones:

.preventpreventDefault(). Almost every @submit wants it.

.stopstopPropagation(). A button inside a clickable card.

.self — only fire if the event target is this element, not a descendant. This is how the demo application's modal backdrop works:

<div class="position-fixed ..." @click.self="confirmDelete = null">

Clicking the dark backdrop closes the dialog; clicking the dialog itself does not, because the event target is then a child. Without .self every click inside the modal would dismiss it.

.once — remove the listener after the first fire.

.passive — tells the browser you will not call preventDefault(), so it need not wait before scrolling. Worth it on @touchmove and @wheel.

They chain, and order matters: @click.prevent.self prevents all clicks and then filters, while @click.self.prevent filters first and only prevents its own.

Key modifiers

For keyboard events, listen for a specific key by name:

      @keydown.enter.prevent="add"
      @keydown.,.prevent="add"
      @keydown.delete="onBackspace"

Three keys, three behaviours, no if (event.key === ...) anywhere. Enter and comma both commit a tag — and both need .prevent, Enter because it would submit the surrounding form and comma because it would otherwise be typed into the box. Backspace is handled without .prevent, because deleting a character is exactly what should happen when the box is not empty.

Any valid KeyboardEvent.key works, kebab-cased: .esc, .tab, .delete, .space, .arrow-up, .page-down. System modifiers are available too — .ctrl, .shift, .alt, .meta — so @keydown.meta.enter is a submit shortcut in one attribute.

Payloads

Emit whatever the parent needs, and prefer the minimum:

emit("view", props.reel.id);                 // an id
emit("update:modelValue", [...props.modelValue, tag]);  // a new array
emit("select", { reel, index });             // several values in one object

Emitting a whole object when the parent only needs an id couples the two components together for no benefit. Emitting several positional arguments is legal but hard to read at the call site — put them in an object.

One rule about arrays and objects: emit a new one, do not mutate the old one. TagInput emits [...props.modelValue, tag] rather than pushing, because the array belongs to the parent and mutating it is the props-are-read-only violation from lesson 10 wearing a disguise.

Why not just pass a callback prop?

You can — :on-like="handleLike" is a function like any other value, and it is how React does this. Events are preferred in Vue because they compose with modifiers, they are declared as part of the component's interface, and a parent can attach several listeners to one event. Use a callback prop when you need a return value, which an emit cannot give you.

Next: v-model and Two-Way Binding — which is exactly a prop and an event, with sugar.