Skip to content

Working with DOM & Browser APIs

JavaScript’s DOM APIs are powerful but notoriously error‑prone. You can query any element, cast it to anything, and call methods that may or may not exist. TypeScript fixes this by providing rich built‑in DOM types, strong event typing, and safe narrowing techniques that prevent runtime crashes.

Let’s explore how to use TypeScript effectively in browser‑based code.


Built‑in DOM Types

TypeScript ships with a full set of type definitions for the browser environment. These include:

  • Document
  • HTMLElement
  • HTMLInputElement
  • HTMLButtonElement
  • NodeList
  • Event, MouseEvent, KeyboardEvent, etc.

Example: Querying elements

const button = document.querySelector("button")

Hover over button in your editor:

const button: HTMLButtonElement | null

TypeScript knows:

  • the element is a HTMLButtonElement
  • it might be null

Example: Querying by ID

const input = document.getElementById("username")

TypeScript infers:

HTMLElement | null

But you often want a more specific type:

const input = document.getElementById("username") as HTMLInputElement

Or safer:

const input = document.querySelector<HTMLInputElement>("#username")

This avoids unsafe casts.


Event Typing

TypeScript provides strong types for DOM events.

Basic event listener

const button = document.querySelector("button")

button?.addEventListener("click", (event) => {
  // event: MouseEvent
  console.log(event.clientX)
})

TypeScript automatically infers:

  • "click"MouseEvent
  • "keydown"KeyboardEvent
  • "input"InputEvent

Keyboard event example

document.addEventListener("keydown", (event) => {
  console.log(event.key) // event: KeyboardEvent
})

Input event example

const input = document.querySelector("input")

input?.addEventListener("input", (event) => {
  console.log(event.data) // event: InputEvent
})

Why this matters

  • No more guessing event types
  • Autocomplete for event properties
  • Prevents calling invalid properties on events

Narrowing Event Targets

One of the most common DOM bugs in JavaScript is assuming the event target is a specific element type.

Example of unsafe JS:

document.addEventListener("input", (e) => {
  console.log(e.target.value) // ❌ Might not exist
})

TypeScript correctly warns:

Property 'value' does not exist on type 'EventTarget'

Safe narrowing with instanceof

document.addEventListener("input", (event) => {
  const target = event.target
  if (target instanceof HTMLInputElement) {
    console.log(target.value) // ✔ Safe
  }
})

Narrowing with type predicates

You can write reusable guards:

function isInput(el: EventTarget | null): el is HTMLInputElement {
  return el instanceof HTMLInputElement
}

document.addEventListener("input", (event) => {
  if (isInput(event.target)) {
    console.log(event.target.value)
  }
})

Narrowing with currentTarget

currentTarget is often safer than target:

button.addEventListener("click", (event) => {
  const btn = event.currentTarget // HTMLButtonElement
  console.log(btn.disabled)
})

TypeScript knows that currentTarget is the element the listener was attached to.


Putting It All Together

Here’s a realistic example combining DOM types, event typing, and narrowing:

const form = document.querySelector("form")
const input = document.querySelector<HTMLInputElement>("#email")

form?.addEventListener("submit", (event) => {
  event.preventDefault()

  if (!input) return

  const value = input.value.trim()

  if (value.includes("@")) {
    console.log("Valid email:", value)
  } else {
    console.log("Invalid email")
  }
})

TypeScript ensures:

  • form and input may be null
  • submit event is a SubmitEvent
  • input.value is always a string
  • No unsafe property access

This is exactly the kind of safety you want in real browser code.


Summary

In this lesson, you learned how TypeScript enhances DOM programming:

1. Built‑in DOM types

  • Strong typing for elements, events, and browser APIs
  • Safer element queries
  • Better autocomplete and refactoring

2. Event typing

  • Automatic inference based on event type
  • Strongly typed event objects
  • No more guessing event properties

3. Narrowing event targets

  • Use instanceof for safe narrowing
  • Use currentTarget when possible
  • Write reusable type guards

These techniques dramatically reduce runtime errors in browser code and make DOM programming far more predictable.