DOM & Event Types
TypeScript's DOM types give every element and event a precise shape — from HTMLInputElement to MouseEvent — so querySelector , addEventListener , and event handlers are fully checked and autocompleted.
Learn DOM & Event Types in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of the DOM as a building full of labelled rooms. querySelector is asking the receptionist for a room by name — they might hand you a key (the element) or shrug because it doesn't exist ( null ). Telling them "I want the input room" (the generic) means the key fits an input's locks specifically. Events are messages slipped under the door: a "click" note carries coordinates, a "keydown" note carries a key — same envelope ( Event ), different contents.
1. Element Types & querySelector Generics
Every DOM element has a precise type — HTMLInputElement , HTMLButtonElement , HTMLCanvasElement , all extending HTMLElement . querySelector takes a generic so you can say which one you expect, and it always returns YourType | null because the element may not be found.
2. Event vs MouseEvent vs KeyboardEvent
Events form a hierarchy : MouseEvent and KeyboardEvent extend the base Event , adding members like clientX or key . When you call addEventListener("click", ...) , TypeScript already knows the handler gets a MouseEvent — no casting needed.
3. Narrowing e.target & Typed Handlers
e.target is deliberately broad — EventTarget | null — because the event could have started on any descendant. To read element properties you narrow it with instanceof (or reach for the more specific e.currentTarget ). The handler-registration pattern itself is captured by a tiny typed event-emitter .
🎯 Your Turn
Narrow an event by its type field, just like you'd narrow a DOM event union. Fill in the two blanks marked ___ , then run it.
No blanks this time — just a brief and a starting outline. Build the dispatcher yourself, run it, and check your output against the example in the comments.
Practice quiz
How do you tell TypeScript the exact element type from querySelector?
- Cast with 'as any'
- Use getElementById
- Use the generic: document.querySelector<HTMLInputElement>("#email")
- It is automatic for every selector
Answer: Use the generic: document.querySelector<HTMLInputElement>("#email"). The generic overload steers the result type, e.g. querySelector<HTMLInputElement>.
What is the result type of 'document.querySelector<HTMLInputElement>("#email")'?
- HTMLInputElement | null
- HTMLInputElement
- Element
- HTMLElement
Answer: HTMLInputElement | null. querySelector returns YourType | null because the element might not be found.
Why is querySelector's result possibly null?
- A bug in the DOM lib
- Because of strict mode only
- It is never null
- The selector might not match any element
Answer: The selector might not match any element. TypeScript types it as YourType | null to force you to handle the not-found case.
What is the return type of document.getElementById("box")?
- HTMLDivElement
- HTMLElement | null
- Element
- any
Answer: HTMLElement | null. getElementById has no generic and returns HTMLElement | null.
Which relationship is correct for event types?
- MouseEvent and KeyboardEvent extend Event
- Event extends MouseEvent
- They are unrelated
- KeyboardEvent extends MouseEvent
Answer: MouseEvent and KeyboardEvent extend Event. MouseEvent and KeyboardEvent both extend the base Event with extra members.
Inside addEventListener("click", handler), what type is the handler's event?
- Event
- KeyboardEvent
- MouseEvent
- any
Answer: MouseEvent. TypeScript infers the correct subtype (MouseEvent) from the known event name 'click'.
Which property does a KeyboardEvent have that the base Event does not?
- clientX
- key
- preventDefault
- target
Answer: key. KeyboardEvent adds members like 'key' and 'code'; clientX belongs to MouseEvent.
What is the type of e.target?
- HTMLElement
- Element
- the specific element always
- EventTarget | null
Answer: EventTarget | null. e.target is EventTarget | null, deliberately broad, so you must narrow it before use.
How do you safely read element-only members off e.target?
- Cast with 'as HTMLInputElement'
- Narrow with 'if (e.target instanceof HTMLInputElement)'
- Ignore the type
- Use e.target.value directly
Answer: Narrow with 'if (e.target instanceof HTMLInputElement)'. instanceof both checks and narrows, far safer than an 'as' cast.
Why does e.currentTarget often not need narrowing while e.target does?
- currentTarget is always null
- target is faster
- currentTarget is the element the listener is attached to, so it can be typed directly
- They are identical
Answer: currentTarget is the element the listener is attached to, so it can be typed directly. currentTarget is the element the listener is on and can be typed; target could be any descendant.