Skip to content
← All questions
Intermediate

How do you type event handlers in Vue templates?

TypeScriptComponents

DOM event handlers in Vue templates receive an Event object, but TypeScript doesn't know which specific element triggered it. You need to type the event parameter explicitly and cast event.target to the correct element type to access properties like .value or .checked.

The problem

ts
// error in strict mode: implicit 'any'
function handleInput(event) {
  console.log(event.target.value)
}

// error: 'value' does not exist on EventTarget
function handleInput(event: Event) {
  console.log(event.target.value)
}

event.target is typed as EventTarget | null, which has no .value property. You need a type assertion.

The solution

vue
<script setup lang="ts">
function handleInput(event: Event) {
  const target = event.target as HTMLInputElement
  console.log(target.value)
}

function handleClick(event: MouseEvent) {
  console.log(event.clientX, event.clientY)
}

function handleKeydown(event: KeyboardEvent) {
  if (event.key === 'Enter') {
    submit()
  }
}

function handleSubmit(event: SubmitEvent) {
  event.preventDefault()
  const form = event.target as HTMLFormElement
  const data = new FormData(form)
}
</script>

<template>
  <input @input="handleInput" />
  <button @click="handleClick">Click</button>
  <input @keydown="handleKeydown" />
  <form @submit="handleSubmit">...</form>
</template>

Event type reference

Template eventTypeScript typeKey properties
@click, @dblclickMouseEventclientX, clientY, button, ctrlKey
@keydown, @keyupKeyboardEventkey, code, ctrlKey, shiftKey
@input, @changeEventtarget (needs cast)
@focus, @blurFocusEventrelatedTarget
@submitSubmitEventsubmitter
@drag, @dropDragEventdataTransfer
@wheelWheelEventdeltaX, deltaY
@touchstart, @touchendTouchEventtouches, changedTouches

Element type assertions

Different form elements need different casts:

ts
function handleTextInput(event: Event) {
  const input = event.target as HTMLInputElement
  console.log(input.value)
}

function handleCheckbox(event: Event) {
  const checkbox = event.target as HTMLInputElement
  console.log(checkbox.checked)
}

function handleSelect(event: Event) {
  const select = event.target as HTMLSelectElement
  console.log(select.value, select.selectedIndex)
}

function handleTextarea(event: Event) {
  const textarea = event.target as HTMLTextAreaElement
  console.log(textarea.value)
}

Inline handlers

For simple cases, cast directly in the template:

vue
<template>
  <input @input="name = ($event.target as HTMLInputElement).value" />
</template>

Or use an inline arrow function:

vue
<template>
  <input @input="(e: Event) => name = (e.target as HTMLInputElement).value" />
</template>

Custom component events

Vue component events (not DOM events) are typed through defineEmits. No casting needed because the payload type is defined by the child:

vue
<!-- ChildComponent.vue -->
<script setup lang="ts">
const emit = defineEmits<{
  select: [item: { id: number; name: string }]
}>()
</script>
vue
<!-- Parent.vue -->
<script setup lang="ts">
function handleSelect(item: { id: number; name: string }) {
  console.log(item.id) // fully typed
}
</script>

<template>
  <ChildComponent @select="handleSelect" />
</template>

target vs currentTarget

ts
function handleClick(event: MouseEvent) {
  // target: the actual element clicked (could be a child)
  const target = event.target as HTMLElement

  // currentTarget: the element the listener is on
  const button = event.currentTarget as HTMLButtonElement
}

If you have a button with a span inside, clicking the span makes target the span and currentTarget the button. Use currentTarget when you want the element the handler is attached to.

Released under the MIT License.