Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to type emits in Vue with TypeScript

Typing emits in Vue with TypeScript ensures parent components pass the correct handler signatures, enables IDE autocompletion for event payload properties, and catches mismatched event names at compile time. As the creator of CoreUI with Vue and TypeScript development experience since 2014, I type every custom event in CoreUI Vue components because it makes the component API self-documenting and prevents subtle bugs where a handler receives the wrong data shape. The recommended approach in Vue 3 <script setup> uses the defineEmits<{...}>() TypeScript syntax with named tuples for each event’s payload. This is more concise than the runtime object syntax and provides better type inference for the returned emit function.

Type emits with TypeScript interface syntax.

<script setup lang="ts">
import { ref } from 'vue'

interface FormData {
  name: string
  email: string
  message: string
}

// TypeScript generic syntax - most recommended
const emit = defineEmits<{
  submit: [data: FormData]          // Single arg named "data"
  cancel: []                        // No args
  error: [message: string]          // Single string arg
  'field-change': [field: keyof FormData, value: string]  // Multiple args
}>()

const form = ref<FormData>({ name: '', email: '', message: '' })

function handleSubmit() {
  emit('submit', form.value) // TypeScript checks FormData shape
}

function handleFieldChange(field: keyof FormData, value: string) {
  emit('field-change', field, value) // TypeScript checks arg types
}
</script>

The object syntax { eventName: [arg1Type, arg2Type] } uses named tuples where each element type represents a positional argument. TypeScript errors if you call emit('submit', wrongType) or emit('unknown-event').

Typing v-model Events

Type the update:modelValue emit for v-model support.

<!-- RangeSlider.vue -->
<script setup lang="ts">
const props = defineProps<{
  modelValue: number
  min?: number
  max?: number
  step?: number
}>()

const emit = defineEmits<{
  'update:modelValue': [value: number]
  change: [value: number, oldValue: number]
}>()

function handleInput(event: Event) {
  const newValue = Number((event.target as HTMLInputElement).value)
  emit('update:modelValue', newValue)
  emit('change', newValue, props.modelValue)
}
</script>

<template>
  <input
    type="range"
    :value="modelValue"
    :min="min"
    :max="max"
    :step="step"
    @input="handleInput"
  />
</template>
<!-- Parent -->
<template>
  <RangeSlider v-model="volume" :max="100" @change="onVolumeChange" />
</template>

The TypeScript type for update:modelValue ensures that v-model on the parent receives a number, not any other type. If the parent’s v-model binding uses a string variable, TypeScript will warn.

Typing Emits in Options API

Use the emits option with explicit types.

import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    submit(payload: { id: number; value: string }): boolean {
      return payload.id > 0 && payload.value.length > 0
    },
    cancel: null
  },

  setup(props, { emit }) {
    function handleSubmit() {
      emit('submit', { id: 1, value: 'test' }) // TypeScript-aware
    }
  }
})

Best Practice Note

In CoreUI Vue components we use the TypeScript generic defineEmits<{...}>() syntax exclusively because it provides better autocompletion and catch-at-compile-time safety. Export emit types from your component file (e.g., export type MyComponentEmits = ...) so parent components can type their handler functions correctly. See how to define emits in Vue 3 with defineEmits for the runtime validation aspects of emit declarations.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Open Link in a New Tab in HTML?
How to Open Link in a New Tab in HTML?

Why does querySelectorAll in TypeScript return Element instead of HTMLElement?
Why does querySelectorAll in TypeScript return Element instead of HTMLElement?

What are the three dots `...` in JavaScript do?
What are the three dots `...` in JavaScript do?

How to loop through an array in JavaScript
How to loop through an array in JavaScript

Answers by CoreUI Core Team