How to define emits in Vue 3 with defineEmits
defineEmits is the <script setup> macro for declaring custom events that a component can emit, replacing the emits option and giving you type-safe emit functions with IDE autocompletion.
As the creator of CoreUI with Vue development experience since 2014, I use defineEmits in every component that communicates with its parent through events, from simple click callbacks to complex form submission payloads.
Declaring emits explicitly serves two purposes: it documents the component’s event API, and it enables Vue to distinguish between custom events and native DOM events on the root element.
The TypeScript generic syntax provides the best developer experience with full type inference on the emitted payload.
Declare emits with runtime validation syntax.
<script setup>
const emit = defineEmits({
// No validation
click: null,
// Validate payload shape
submit: (data) => {
return data.name && data.email
},
// Emit with multiple args
change: (value, oldValue) => typeof value === 'number'
})
function handleSubmit() {
emit('submit', { name: 'Alice', email: '[email protected]' })
}
function handleChange(val, old) {
emit('change', val, old)
}
</script>
Validator functions return true for valid payloads. Vue logs a warning in development when validation fails. The validators are runtime only — they don’t provide TypeScript type inference on the calling side.
TypeScript Generic Syntax
Full type safety with TypeScript.
<script setup lang="ts">
interface User {
id: number
name: string
email: string
}
const emit = defineEmits<{
submit: [user: User] // Single arg
cancel: [] // No args
change: [value: number, old: number] // Multiple args
'update:modelValue': [value: string] // v-model support
}>()
function handleSubmit(user: User) {
emit('submit', user) // TypeScript checks the payload type
}
function handleUpdate(val: string) {
emit('update:modelValue', val) // Enables v-model on this component
}
</script>
The object syntax uses event names as keys and tuple types as values. TypeScript will error if you call emit('submit', wrongType). The update:modelValue convention enables using v-model on the component.
Two-Way Binding with v-model
Implement v-model support using defineProps + defineEmits.
<!-- NumberInput.vue -->
<template>
<input
type="number"
:value="modelValue"
@input="emit('update:modelValue', Number($event.target.value))"
/>
</template>
<script setup lang="ts">
defineProps<{ modelValue: number }>()
const emit = defineEmits<{ 'update:modelValue': [value: number] }>()
</script>
<!-- Parent.vue -->
<template>
<!-- v-model works because of the prop/emit convention -->
<NumberInput v-model="quantity" />
</template>
<script setup>
import { ref } from 'vue'
const quantity = ref(0)
</script>
v-model on a component is syntactic sugar for :modelValue="quantity" @update:modelValue="quantity = $event". Matching the prop name and emit name to this convention enables v-model.
Best Practice Note
In CoreUI Vue components we always declare defineEmits with TypeScript types — it makes the component’s public API self-documenting and prevents event name typos that cause silent failures. Always declare emits even when no validation is needed, as it tells Vue the event is intentional and suppresses warnings about unknown event listeners. See how to define props in Vue 3 with defineProps for the complementary input declaration pattern.



