# How to Use Vue Emits in Vue 3: Best Practices for Child-to-Parent Communication

> Master Vue emits in Vue 3 for seamless child-to-parent communication. Learn best practices with defineEmits, naming conventions, and runtime validators for error-free development.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: best-practices
- Published: 2026-02-16

---

**Use `defineEmits` to explicitly declare events in `<script setup>`, emit with camelCase in JavaScript, listen with kebab-case in templates, and leverage runtime validators to catch payload errors during development.**

Vue emits serve as the standard mechanism for child-to-parent communication in Vue 3, replacing the implicit event bus patterns of Vue 2 with a type-safe, compiler-assisted approach. According to the vuejs/core source code, the `defineEmits` macro and runtime validation system in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts) provide compile-time inference and dev-only warnings that prevent silent failures.

## Declaring Vue Emits with `defineEmits`

The `defineEmits` macro is the primary way to declare events in the Composition API. It is a compile-time construct that disappears after build, leaving only the runtime emit function. In [`packages/runtime-core/src/apiSetupHelpers.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiSetupHelpers.ts) (lines 134-148), the macro provides overloads for both array and object syntax.

### Array Syntax for Simple Declarations

Use the array syntax when you only need to declare event names without payload validation:

```typescript
<script setup lang="ts">
const emit = defineEmits(['save', 'cancel'])

function onSave() {
  emit('save', { id: 42 })
}

function onCancel() {
  emit('cancel')
}
</script>

```

*Source*: [`packages/runtime-core/src/apiSetupHelpers.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiSetupHelpers.ts) lines 134-138.

### Object Syntax with Validators

Use the object syntax when you need runtime payload validation. The validator function receives the emitted payload and should return a boolean indicating validity:

```typescript
<script setup lang="ts">
const emit = defineEmits({
  change: (value: number) => typeof value === 'number',
  submit: (payload: { name: string }) => !!payload.name,
})

// Correct payload – passes validator
emit('change', 10)

// Incorrect payload – triggers dev warning
emit('change', 'oops')
</script>

```

*Source*: [`packages/runtime-core/src/apiSetupHelpers.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiSetupHelpers.ts) lines 139-142.

## Runtime Validation and Error Handling

The internal `emit` function in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts) (lines 24-55) performs several critical checks before invoking parent listeners. It validates the event against the component's `emitsOptions`, warns about undeclared events in development mode, and handles the `once` modifier by tracking emitted events in `instance.emitted` (lines 124-132).

When an event fails validation, Vue logs a helpful warning indicating the component name and the invalid payload, preventing silent failures that were common in Vue 2.

## Naming Conventions: camelCase vs kebab-case

Vue normalizes event names to kebab-case when used in templates because HTML attributes are case-insensitive. The `emit` function contains a dev-only check (lines 76-89 in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts)) that warns when a camelCase event is emitted but the parent listens with a kebab-case name.

**Best practice**: Emit with camelCase in JavaScript and listen with kebab-case in templates.

```vue
<!-- ChildComponent.vue -->
<script setup>
const emit = defineEmits(['updateData'])
emit('updateData', newValue)
</script>

<!-- Parent.vue -->
<template>
  <ChildComponent @update-data="handleUpdate" />
</template>

```

## Handling v-model Modifiers Automatically

The internal `emit` function automatically detects `update:` events and applies `number` and `trim` modifiers (lines 61-69 in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts)). This means child components do not need manual modifier handling when implementing `v-model`.

```vue
<!-- Child -->
<script setup>
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
  emit('update:modelValue', e.target.value) // .number modifier handled automatically
}
</script>

<!-- Parent -->
<Child v-model.number="count" />

```

## Options API: The `emits` Option and `$emit`

For Options API components, declare events using the `emits` option defined in [`packages/runtime-core/src/componentOptions.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentOptions.ts) (line 161). Access the emit function via `this.$emit`, which is exposed on the public instance in [`packages/runtime-core/src/componentPublicInstance.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentPublicInstance.ts) (lines 315-317).

```typescript
export default {
  emits: ['update', 'delete'],
  methods: {
    requestUpdate(data) {
      this.$emit('update', data)
    },
    requestDelete(id) {
      this.$emit('delete', id) // Dev warning if event not declared
    }
  }
}

```

## Performance Considerations

Vue caches normalized emit options using `normalizeEmitsOptions` and an internal `emitsCache` (referenced in [`packages/runtime-core/src/apiCreateApp.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiCreateApp.ts) line 192). This ensures that components with many mixins or complex inheritance chains do not pay a normalization penalty on every instance.

## Summary

- **Always declare events** using `defineEmits` in `<script setup>` or the `emits` option in Options API to enable dev-time validation and warnings.
- **Use object syntax with validators** when you need runtime payload type checking.
- **Emit camelCase, listen kebab-case** to align with HTML attribute conventions and avoid dev warnings.
- **Leverage automatic modifier handling** for `v-model` by emitting `update:modelValue` without manual transformation logic.
- **Avoid `$emit` in `<script setup>`**; use the typed `emit` function returned by `defineEmits` instead.
- **Trust the caching mechanism**; Vue automatically optimizes emit option normalization across component instances.

## Frequently Asked Questions

### What happens if I emit an event that wasn't declared in `defineEmits`?

Vue will trigger a development-mode warning. The internal `emit` function in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts) checks the event name against the component's `emitsOptions` (lines 24-55) and logs a warning if the event is not registered, helping catch typos and API mismatches early.

### Should I use `this.$emit` inside `<script setup>`?

No. Inside `<script setup>`, always use the `emit` function returned by `defineEmits`. The `$emit` property on the public instance (defined in [`packages/runtime-core/src/componentPublicInstance.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentPublicInstance.ts) lines 315-317) is intended for Options API components. Using the scoped `emit` ensures TypeScript correctly infers payload types.

### How do I validate event payloads at runtime?

Use the object syntax in `defineEmits` and provide validator functions. Each key should be the event name, and the value should be a function that receives the payload and returns a boolean. According to the implementation in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts) (lines 40-48), if a validator returns false, Vue emits a development warning indicating the component and the invalid payload.

### Do I need to manually handle `v-model` modifiers like `.trim` or `.number`?

No. When you emit an `update:modelValue` event (or any `update:` prefixed event), Vue's internal `emit` function automatically applies `trim` and `number` modifiers if the parent used them (as seen in [`packages/runtime-core/src/componentEmits.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/componentEmits.ts) lines 61-69). Simply emit the raw value and let Vue handle the transformation.