100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Emitting Custom Events

Learn how child components communicate upward to parents using Vue's custom event system, including typed emits and the defineEmits macro.

ComponentsBeginner8 min readJul 9, 2026
Analogies

Emitting Custom Events

Vue's component model is built around one-way data flow: props travel down from parent to child, and events travel back up from child to parent. When a child component needs to notify its parent that something happened — a button was clicked, a form was submitted, a value changed — it does so by emitting a custom event. The parent then listens for that event using the v-on (@) directive, exactly as it would listen for a native DOM event like click. This pattern keeps components decoupled: a child never reaches into its parent's state directly, it simply announces what happened and lets the parent decide how to react.

🏏

Cricket analogy: Vue's one-way flow is like a captain sending fielding instructions down to a fielder, while the fielder only signals back 'catch taken' rather than repositioning the captain directly — the child announces what happened and the captain decides the next call.

Declaring emits with defineEmits

In <script setup>, you declare the events a component can emit using the defineEmits compiler macro. This is not strictly required to make emitting work, but it is strongly recommended because it documents the component's public event contract, enables IDE autocompletion, and lets Vue warn you in development if you emit an event that isn't declared. defineEmits returns an emit function you call with the event name and any payload arguments.

🏏

Cricket analogy: defineEmits is like a fielder formally telling the captain beforehand 'I only ever signal catch-taken or run-out appeal' — not required to field, but it documents the fielder's exact repertoire of signals so the whole team knows what to expect.

vue
<script setup>
import { ref } from 'vue'

const emit = defineEmits(['update-quantity', 'remove-item'])
const quantity = ref(1)

function increment() {
  quantity.value++
  emit('update-quantity', quantity.value)
}

function removeFromCart() {
  emit('remove-item')
}
</script>

<template>
  <div class="cart-line">
    <button @click="increment">+</button>
    <span>{{ quantity }}</span>
    <button @click="removeFromCart">Remove</button>
  </div>
</template>

Listening from the parent

The parent listens using v-on/@ with the exact event name declared by the child (event names are commonly kebab-case in templates). Any arguments passed to emit() become the arguments received by the handler. This mirrors how native DOM events work, which keeps the mental model consistent whether you're handling a click on a <button> or a custom update-quantity from a child component.

🏏

Cricket analogy: The captain listens for the fielder's exact call — 'catch-taken', in the lowercase style used on the field — and whatever details the fielder shouts, like which hand, become the arguments the captain's decision uses, just like reacting to any umpire signal.

vue
<template>
  <CartLine
    v-for="item in items"
    :key="item.id"
    @update-quantity="onQuantityChange(item.id, $event)"
    @remove-item="onRemove(item.id)"
  />
</template>

Validating event payloads

Like props, emits can be declared with validation using the object syntax instead of an array. Each key maps to a validator function that returns true if the payload is valid, or false to trigger a console warning in development. This is especially useful for events that carry structured payloads, since it catches contract violations early during development rather than surfacing as subtle bugs downstream.

🏏

Cricket analogy: Validating an emit with a validator function is like a captain requiring that a fielder's 'catch-taken' signal include a valid fielder name and ball number, flagging a warning in the team huddle if the signal is malformed rather than letting a bad call slip through unnoticed.

javascript
const emit = defineEmits({
  'update-quantity': (newQty) => {
    return typeof newQty === 'number' && newQty >= 0
  },
  'remove-item': null // no validation needed
})

In TypeScript projects, you can type emits with a generic type argument instead: defineEmits<{ 'update-quantity': [qty: number]; 'remove-item': [] }>(). This gives full compile-time checking of both event names and payload shapes, something Angular's @Output() EventEmitter<T> approximates but React has no direct equivalent for, since React relies on plain callback props instead of a dedicated event system.

A common pitfall is emitting an event name in camelCase (e.g. emit('updateQuantity', ...)) but listening for it in the parent template with the same camelCase (@updateQuantity). This actually works because Vue templates are case-insensitive for HTML attributes, but if you ever bind the listener dynamically via a render function or JSX, camelCase and kebab-case are NOT automatically reconciled — pick kebab-case for emitted event names to avoid surprises across both templates and render functions.

  • Events flow up from child to parent, complementing props which flow down — together they form Vue's unidirectional data flow.
  • Use defineEmits in <script setup> to declare and document a component's event contract, returning an emit() function.
  • Parents listen with v-on/@eventName, and any extra arguments to emit() are passed through to the handler.
  • Emits can be validated using the object syntax, similar to prop validators, to catch malformed payloads in development.
  • Prefer kebab-case event names in templates for consistency between template usage and render-function usage.
  • Declaring emits (even though optional) improves IDE support and lets Vue warn about undeclared or misused events.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#VueJsStudyNotes#WebDevelopment#EmittingCustomEvents#Emitting#Custom#Events#Declaring#StudyNotes#SkillVeris