Events are the other half of the component contract: props go down, events go up.
A child talks back to its parent by emitting events. Declare them with the defineEmits macro, which returns an emit function:
const emit = defineEmits(['remove', 'rename'])
emit('rename', newName)The parent listens exactly like a DOM event: <TodoItem @remove="deleteTodo(todo.id)" @rename="(name) => todo.name = name" />. Extra arguments passed to emit become the handler's arguments. Events emitted in camelCase (emit('itemSelected')) can be listened to as @item-selected.
In the template you can emit directly with $emit('remove').
With TypeScript, type the payloads with the named-tuple syntax:
const emit = defineEmits<{ rename: [name: string]; remove: [] }>()Declaring events is not just documentation. Listeners for undeclared events fall through to the component's root element as native listeners (see attribute fallthrough), which can make a click fire twice. Declared events are also validated in dev and show up in devtools.
Component events do not bubble: only the direct parent can listen. For distant communication use provide/inject or a store instead of passing events up through many layers.
A useful convention: name events after what happened (select, close, update:modelValue), not after what the parent should do.
Example
<!-- RenameField.vue -->
<script setup>
import { ref } from 'vue'
const { initial } = defineProps({ initial: String })
const emit = defineEmits(['rename', 'cancel'])
const draft = ref(initial)
</script>
<template>
<input v-model="draft" @keyup.enter="emit('rename', draft.trim())">
<button @click="$emit('cancel')">Cancel</button>
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import RenameField from './RenameField.vue'
const title = ref('Inbox')
const editing = ref(true)
function onRename(name) { title.value = name; editing.value = false }
</script>
<template>
<RenameField v-if="editing" :initial="title"
@rename="onRename" @cancel="editing = false" />
<h2 v-else>{{ title }}</h2>
</template>Remember
const emit = defineEmits([...]), thenemit('name', payload)- Parents listen with
@name; payload arguments go to the handler - Component events don't bubble; declare all emitted events
Watch out for
- Emitting an undeclared
clickevent while the root element also fires a native click, so the parent handler runs twice.