Recently, while debugging a form click event in one of my TypeScript projects, I observed that when event types aren’t clearly defined, it is challenging to debug events. I wasn’t sure how to correctly define button clicks, key presses, or form submissions. Sometimes my code worked, but I didn’t get autocomplete suggestions or helpful error messages when something was wrong.
Later, I learned that giving the right type to each event makes a big difference. It helps you write cleaner code, catch mistakes early, and makes everything easier to understand.
In this article, I’ll explain how to work with different types of events in TypeScript. We’ll learn about various browser events, including clicks and form submissions, custom events, and even events in React.
Understanding Event Types in TypeScript
Basic DOM Events
The simplest way to handle DOM events is by using the built-in event types that TypeScript provides. Here’s a basic example:
// Adding a click event listener to a button
const button = document.querySelector('button');
button.addEventListener('click', (event: MouseEvent) => {
console.log('Button clicked!');
console.log('Mouse position:', event.clientX, event.clientY);
});TypeScript provides several event types that correspond to different DOM events:
- MouseEvent for mouse-related events
- KeyboardEvent for keyboard interactions
- DragEvent for drag-and-drop operations
- FocusEvent for focus/blur events
- TouchEvent for touch interactions
Check out: Conditionally Add Property to Object in TypeScript
Typing Event Handlers
When working with event handlers, it’s important to properly type the function. Here’s how I usually do it:
// main.ts
// Defining a typed event handler
function handleClick(event: MouseEvent): void {
event.preventDefault();
console.log('Clicked at position:', event.offsetX, event.offsetY);
}
// Using the handler
document.getElementById('myButton')?.addEventListener('click', handleClick);
<-------Index.html code ---------->
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>TypeScript Event Handler</title>
</head>
<body>
<button id="myButton">Click Me</button>
<!-- Link to the compiled JavaScript -->
<script src="main.js"></script>
</body>
</html>You can also use the generic EventListener type:
const keyHandler: EventListener = (event: Event) => {
if (event instanceof KeyboardEvent) {
console.log('Key pressed:', event.key);
}
};
window.addEventListener('keydown', keyHandler);Output:

Working with HTML Element-Specific Events
Different HTML elements trigger different types of events. TypeScript provides specific types for these cases.
Form Events Example
When handling form submissions, the HTMLFormElement and SubmitEvent types come in handy:
//main.ts
const contactForm = document.getElementById('contactForm') as HTMLFormElement;
contactForm.addEventListener('submit', (event: SubmitEvent) => {
event.preventDefault();
// Access form data using FormData API
const formData = new FormData(contactForm);
const name = formData.get('name') as string;
const email = formData.get('email') as string;
console.log(`Submission from ${name} (${email})`);
});Index.html for UI.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Event Example</title>
</head>
<body>
<form id="contactForm">
<label>
Name:
<input type="text" name="name" required />
</label><br />
<label>
Email:
<input type="email" name="email" required />
</label><br />
<button type="submit">Submit</button>
</form>
<!-- Link to compiled TypeScript -->
<script src="main.js"></script>
</body>
</html>Output:

Check out: Check if an Object Has a Property in TypeScript
Input Events Example
For handling input changes, you can use the HTMLInputElement type:
const zipCodeInput = document.getElementById('zipCode') as HTMLInputElement;
zipCodeInput.addEventListener('input', (event: Event) => {
const input = event.target as HTMLInputElement;
// Validate US zip code (5 digits)
const isValidZip = /^\d{5}$/.test(input.value);
if (isValidZip) {
input.classList.remove('error');
// Perhaps fetch city/state based on zip code
} else {
input.classList.add('error');
}
});Custom Events in TypeScript
Sometimes the built-in event types aren’t enough, especially when working with custom events. Here’s how I create and type custom events:
Creating Custom Event Types
//Index.ts
interface PaymentCompletedEvent extends CustomEvent {
detail: {
transactionId: string;
amount: number;
timestamp: Date;
};
}
function processPayment(amount: number): void {
const transactionId = 'TXN-' + Math.random().toString(36).substring(2, 10);
const paymentEvent = new CustomEvent<PaymentCompletedEvent['detail']>('paymentCompleted', {
detail: {
transactionId,
amount,
timestamp: new Date()
},
bubbles: true
});
document.dispatchEvent(paymentEvent);
}
document.addEventListener('paymentCompleted', ((event: Event) => {
const paymentEvent = event as PaymentCompletedEvent;
const { transactionId, amount, timestamp } = paymentEvent.detail;
console.log(`💰 Payment of $${amount} completed at ${timestamp.toLocaleString()}`);
console.log(`🔑 Transaction ID: ${transactionId}`);
}) as EventListener);
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById('payBtn');
button?.addEventListener('click', () => processPayment(199.99));
});After this, compile the typescript using npx tsc. Then, to see the output in UI, create an index.html file.
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<h1>Payment Event Demo</h1>
</head>
<body>
<button id="payBtn">Pay Now</button>
<script type="module" src="./dist/index.js"></script>
</body>
</html>Run the bast with npx live-server, then open the browser to http://127.0.0.1:5500/index.html.
Output:

Check out: Typescript Iterate Over Records
Event Types in React
If you’re working with React, TypeScript provides specific types for React events.
Basic React Event Types
import React, { useState } from 'react';
const SubscriptionForm: React.FC = () => {
const [email, setEmail] = useState('');
// React's ChangeEvent type
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value);
};
// React's FormEvent type
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(`Subscribing email: ${email}`);
// Call API to subscribe user
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Subscribe to our newsletter:</label>
<input
type="email"
id="email"
value={email}
onChange={handleEmailChange}
placeholder="[email protected]"
required
/>
<button type="submit">Subscribe</button>
</form>
);
};Now, update the App.tsx file.
import React from 'react';
import SubscriptionForm from './SubscriptionForm';
const App = () => (
<div>
<h1>Welcome to My Newsletter</h1>
<SubscriptionForm />
</div>
);
export default App;Run the code in the terminal, npm run dev.
Output:

Check out: Do-While Loop in TypeScript
Common React Event Types
Here are some common React event types I use regularly:
//EventDemo.tsx
import React, { useState } from 'react';
const EventDemo: React.FC = () => {
const [inputValue, setInputValue] = useState('');
// Mouse event
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log('Button clicked:', event.currentTarget.name);
};
// Keyboard event
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
console.log('Enter key pressed:', inputValue);
}
};
// Focus event
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
console.log('Input focused:', event.currentTarget.name);
};
// Drag event
const handleDrag = (event: React.DragEvent<HTMLDivElement>) => {
console.log('Element is being dragged');
};
return (
<div>
<h2>React Event Handling Example</h2>
{/* Focus + Keyboard */}
<input
type="text"
name="demoInput"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onFocus={handleFocus}
onKeyDown={handleKeyPress}
placeholder="Type and press Enter"
/>
<br /><br />
{/* Mouse */}
<button name="submitBtn" onClick={handleClick}>
Click Me
</button>
<br /><br />
{/* Drag */}
<div
draggable
onDrag={handleDrag}
style={{
width: '150px',
height: '100px',
backgroundColor: '#b3d9ff',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'grab',
}}
>
Drag Me
</div>
</div>
);
};
export default EventDemo;Update the App.tsx file
import React from 'react';
import EventDemo from './EventDemo';
const App = () => (
<div>
<EventDemo />
</div>
);
export default App;Output:

Check out: Show Alerts in TypeScript
Using Event Type Assertions
Sometimes TypeScript can’t infer the exact event type, especially when working with third-party libraries. In such cases, type assertions come in handy:
// When TypeScript doesn't know the exact type
someElement.addEventListener('customEvent', (event: Event) => {
// Type assertion to access custom properties
const customEvent = event as CustomEvent<{userId: string}>;
const userId = customEvent.detail.userId;
fetchUserData(userId).then(userData => {
// Process user data
});
});Generic Event Type Utility
I’ve found creating utility types for events to be very helpful in larger projects:
// A generic event handler type
type EventHandler<T extends Event> = (event: T) => void;
// Usage examples
const clickHandler: EventHandler<MouseEvent> = (event) => {
// TypeScript knows this is a MouseEvent
console.log(event.clientX, event.clientY);
};
const keyHandler: EventHandler<KeyboardEvent> = (event) => {
// TypeScript knows this is a KeyboardEvent
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveDocument();
}
};Event Delegation with TypeScript
Event delegation is a powerful pattern, and TypeScript can help make it type-safe:
// Event delegation for a list of items
const shoppingList = document.getElementById('shopping-list');
shoppingList?.addEventListener('click', (event: MouseEvent) => {
const target = event.target as HTMLElement;
// Check if a delete button was clicked
if (target.matches('.delete-item')) {
const itemId = target.closest('li')?.dataset.itemId;
if (itemId) {
console.log(`Removing item ${itemId} from shopping list`);
// Remove the item from the DOM and maybe from a backend
target.closest('li')?.remove();
}
}
// Check if the item checkbox was clicked
if (target.matches('input[type="checkbox"]')) {
const checkbox = target as HTMLInputElement;
const itemId = checkbox.closest('li')?.dataset.itemId;
if (itemId) {
console.log(`Marking item ${itemId} as ${checkbox.checked ? 'completed' : 'pending'}`);
// Update item status
}
}
});Output:

Check out: React’s useContext Hook with TypeScript
Conclusion
Properly typing events in TypeScript significantly improves your development experience and code quality. It provides better autocompletion, catches errors at compile time, and makes your code more self-documenting.
I’ve found that investing time in learning these patterns has made my TypeScript code much more robust and maintainable over the years.
Whether you’re working with DOM events, custom events, or React events, TypeScript has you covered with comprehensive type definitions. By using the techniques I’ve shared, you’ll be able to handle events with confidence in your TypeScript applications.

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.