Skip to content

Emits and defineEmits

Props carry data down from parent to child. When a child needs to communicate back up — “the user clicked this person”, “the form was submitted” — it emits an event. The parent listens and responds.

defineEmits is a compiler macro that declares the events a component can emit, with TypeScript types for their payloads:

PersonCard.vue
<script setup lang="ts">
const props = defineProps<{
id: string
name: string
clickable?: boolean
}>()
const emit = defineEmits<{
select: [id: string]
}>()
function handleClick() {
if (props.clickable) {
emit('select', props.id)
}
}
</script>
<template>
<div
class="person-card"
:class="{ clickable }"
@click="handleClick"
>
{{ name }}
</div>
</template>

emit('select', props.id) fires the select event with the person’s id as the payload.

In the parent, use v-on (or @) to listen:

FocusView.vue
<script setup lang="ts">
import PersonCard from '../PersonCard/PersonCard.vue'
function handleSelect(id: string) {
console.log('Selected person:', id)
// update focus, navigate, etc.
}
</script>
<template>
<PersonCard
id="abc-123"
name="Alice"
:clickable="true"
@select="handleSelect"
/>
</template>

@select="handleSelect" — when PersonCard emits select, Vue calls handleSelect with the payload. TypeScript knows id is a string because of the defineEmits declaration.

For simple cases, the handler can be inline:

<PersonCard
:id="person.id"
:name="person.name"
:clickable="true"
@select="(id) => store.setFocus(id)"
/>

Vue uses kebab-case for event names in templates: @select, @form-submit, @date-changed. In defineEmits, use camelCase: formSubmit — Vue handles the conversion automatically.

The parent owns the data. The child emits intent (“user selected this”). The parent decides what to do with that intent. This separation makes components predictable and reusable — the same PersonCard can be used in different contexts where clicking means different things.

  1. Add an id prop to PersonCard.vue.
  2. Add a select emit that fires with the id payload when the card is clicked and clickable is true.
  3. In App.vue, render three PersonCard instances with different ids and a @select handler that logs the selected id.
  4. Verify in the browser console that clicking a card logs its id.
  • defineEmits<{ eventName: [payload type] }>() declares typed events a component can emit.
  • emit('eventName', payload) fires the event from the child.
  • The parent listens with @event-name="handler" — the handler receives the payload.
  • Props go down; events go up — this one-way flow keeps components predictable.