Files
agent-skills/skills/vue-best-practices/reference/ts-event-handler-explicit-typing.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

214 lines
5.8 KiB
Markdown

---
title: Always Explicitly Type Event Handlers
impact: MEDIUM
impactDescription: Without explicit typing, event parameters have implicit 'any' type causing TypeScript errors in strict mode
type: gotcha
tags: [vue3, typescript, events, dom-events, composition-api]
---
# Always Explicitly Type Event Handlers
**Impact: MEDIUM** - Native DOM event handlers in Vue components have implicit `any` type for the `event` parameter. In TypeScript strict mode, this causes errors. You must explicitly type event parameters and use type assertions for `event.target`.
## Task Checklist
- [ ] Always type the `event` parameter explicitly (e.g., `Event`, `MouseEvent`)
- [ ] Use type assertions when accessing element-specific properties
- [ ] Consider using inline handlers for simple cases
- [ ] Be aware of Vue's synthetic event system
## The Problem
```vue
<script setup lang="ts">
// WRONG: event has implicit 'any' type
function handleChange(event) { // Error in strict mode!
console.log(event.target.value) // Also error: target might be null
}
// WRONG: Missing type assertion for element access
function handleInput(event: Event) {
console.log(event.target.value) // Error: 'value' doesn't exist on EventTarget
}
</script>
<template>
<input @change="handleChange" @input="handleInput" />
</template>
```
## The Solution
```vue
<script setup lang="ts">
// CORRECT: Explicit Event type + type assertion
function handleChange(event: Event) {
const target = event.target as HTMLInputElement
console.log(target.value)
}
// CORRECT: Specific event type when needed
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY)
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
submitForm()
}
}
function handleSubmit(event: SubmitEvent) {
event.preventDefault()
const formData = new FormData(event.target as HTMLFormElement)
}
</script>
<template>
<input @change="handleChange" />
<button @click="handleClick">Click</button>
<input @keydown="handleKeydown" />
<form @submit="handleSubmit">...</form>
</template>
```
## Common Event Types
| Event | Type | Common Properties |
|-------|------|-------------------|
| click, dblclick | `MouseEvent` | clientX, clientY, button |
| keydown, keyup, keypress | `KeyboardEvent` | key, code, ctrlKey, shiftKey |
| input, change | `Event` | target (needs assertion) |
| focus, blur | `FocusEvent` | relatedTarget |
| submit | `SubmitEvent` | submitter |
| drag, dragstart, drop | `DragEvent` | dataTransfer |
| wheel, scroll | `WheelEvent` | deltaX, deltaY |
| touch events | `TouchEvent` | touches, changedTouches |
## Element-Specific Type Assertions
```vue
<script setup lang="ts">
// HTMLInputElement for text, number, checkbox, radio inputs
function handleTextInput(event: Event) {
const input = event.target as HTMLInputElement
console.log(input.value, input.checked)
}
// HTMLSelectElement for select dropdowns
function handleSelect(event: Event) {
const select = event.target as HTMLSelectElement
console.log(select.value, select.selectedIndex)
}
// HTMLTextAreaElement for textareas
function handleTextarea(event: Event) {
const textarea = event.target as HTMLTextAreaElement
console.log(textarea.value, textarea.selectionStart)
}
// HTMLFormElement for forms
function handleFormSubmit(event: SubmitEvent) {
event.preventDefault()
const form = event.target as HTMLFormElement
const formData = new FormData(form)
}
</script>
```
## Inline Event Handler Pattern
For simple cases, inline handlers with type annotations work well:
```vue
<template>
<!-- Inline with type assertion -->
<input
@input="(event: Event) => {
const input = event.target as HTMLInputElement
searchQuery = input.value
}"
/>
<!-- Or with $event cast -->
<input @input="searchQuery = ($event.target as HTMLInputElement).value" />
</template>
```
## Generic Handler Pattern
Create reusable typed handlers:
```typescript
// utils/events.ts
export function getInputValue(event: Event): string {
return (event.target as HTMLInputElement).value
}
export function getSelectValue(event: Event): string {
return (event.target as HTMLSelectElement).value
}
export function getCheckboxChecked(event: Event): boolean {
return (event.target as HTMLInputElement).checked
}
```
```vue
<script setup lang="ts">
import { getInputValue, getCheckboxChecked } from '@/utils/events'
const name = ref('')
const agreed = ref(false)
</script>
<template>
<input @input="e => name = getInputValue(e)" />
<input type="checkbox" @change="e => agreed = getCheckboxChecked(e)" />
</template>
```
## Vue Component Events
For Vue component events (not DOM events), use `defineEmits` for type safety:
```vue
<script setup lang="ts">
const emit = defineEmits<{
'custom-event': [data: { id: number; name: string }]
}>()
// Handler for child component event
function handleChildEvent(data: { id: number; name: string }) {
console.log(data.id, data.name)
}
</script>
<template>
<!-- Custom component event - properly typed -->
<ChildComponent @custom-event="handleChildEvent" />
</template>
```
## Avoiding currentTarget vs target Confusion
```typescript
function handleClick(event: MouseEvent) {
// target: The element that triggered the event (could be a child)
const target = event.target as HTMLElement
// currentTarget: The element the listener is attached to
const currentTarget = event.currentTarget as HTMLButtonElement
// Be explicit about which you need
if (target.tagName === 'SPAN') {
// Clicked on span inside button
}
}
```
## Reference
- [Vue.js TypeScript with Composition API - Event Handlers](https://vuejs.org/guide/typescript/composition-api.html#typing-event-handlers)
- [MDN Event Reference](https://developer.mozilla.org/en-US/docs/Web/Events)
- [TypeScript DOM Types](https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts)