Typing DOM Elements
Use instanceof to pick the right DOM element type — like choosing the right CCTV lens at a checkpost.
When working with the DOM in TypeScript, every element you select comes with a type. But not all types are created equal. document.getElementById() returns HTMLElement | null. document.querySelector() returns Element | null. These generic types are correct — but they don't give you access to element-specific properties like .value, .src, or .disabled. To access those, you need to narrow the type to the SPECIFIC element type.
Think of the CCTV cameras at an RTA checkpost in Hyderabad. The control room has dozens of screens showing different feeds. But you don't look at just any screen to check a specific vehicle — you choose the right camera with the right LENS:
- The wide-angle lens shows you that there ARE vehicles (this is
HTMLElement— it tells you it's a DOM element, but not what kind). - The zoom lens focused on the license plate shows you the specific vehicle number (
HTMLInputElement— gives you access to.value). - The traffic light camera shows you the signal status (
HTMLButtonElement— gives you.disabled).
instanceof is the tool that lets you switch from the wide-angle lens to the zoom lens. Before the check, you only know it's an HTMLElement — it could be anything. After instanceof HTMLInputElement, you KNOW it's an input, and TypeScript gives you access to all input-specific properties. Without this narrowing, you're working with the generic HTMLElement type — it tells you the element exists, but not what you can do with it.
TypeScript comes with a comprehensive set of DOM type definitions. Every HTML element has a corresponding interface that extends HTMLElement:
// Common element types:
HTMLDivElement // <div>
HTMLInputElement // <input>
HTMLButtonElement // <button>
HTMLAnchorElement // <a>
HTMLImageElement // <img>
HTMLFormElement // <form>
HTMLSelectElement // <select>
HTMLTextAreaElement // <textarea>
HTMLParagraphElement // <p>
HTMLSpanElement // <span>
HTMLUListElement // <ul>
HTMLLIElement // <li>
// And many more — literally every
// HTML element has a type!
These types live in the lib.dom.d.ts file that ships with TypeScript. You don't need to install anything extra. They are automatically available in any TypeScript project targeting a browser environment.
Let's see how different selection methods return different types:
// getElementById — specific but narrow
const el =
document.getElementById("app");
// Type: HTMLElement | null
// querySelector — generic
const btn =
document.querySelector(".btn");
// Type: Element | null
// querySelectorAll — NodeList
const items =
document.querySelectorAll(".item");
// Type: NodeListOf<Element>
// Type-specific selectors don't help
// TypeScript can't parse CSS selectors:
The key difference between HTMLElement and Element: HTMLElement extends Element and adds HTML-specific properties and methods. Element is more generic — it covers both HTML and SVG elements. In most web development, you'll be working with HTMLElement subclasses, so prefer methods that return HTMLElement (like getElementById) when possible.
Once you have an element reference, you need to access its properties. This is where type narrowing becomes essential. Let's walk through the safe patterns:
Step 1: Null Check
const input =
document.getElementById("name");
// Type: HTMLElement | null
if (!input) {
// Handle missing element
console.warn("Input not found");
return;
}
// After null check: input is
// narrowed to HTMLElement
Step 2: instanceof Check for Specific Type
if (input instanceof HTMLInputElement) {
// input is now HTMLInputElement!
// .value, .type, .checked, .placeholder
// are all available!
console.log(input.value);
input.value = "Hello";
// TypeScript blocks this:
// input.src — only HTMLImageElement has .src
}
Alternative: Type Assertion (use sparingly!)
// When you're SURE of the type
const input =
document.getElementById("name") as HTMLInputElement;
// ⚠️ No null check, no instanceof!
// Use only when you have certainty
// from external knowledge
// Slightly safer with null check:
const el =
document.getElementById("name");
const input =
el as HTMLInputElement | null;
if (input) {
// Still assumes it's HTMLInputElement
input.value;
}
Working with Multiple Elements:
const buttons =
document.querySelectorAll("button");
// Type: NodeListOf<HTMLButtonElement>
// querySelectorAll with tag name
// gives SPECIFIC type!
buttons.forEach(btn => {
// btn is HTMLButtonElement
btn.disabled = true;
});
// With class selector, you lose specificity:
const els =
document.querySelectorAll(".btn");
// Type: NodeListOf<Element>
// Must narrow each one:
els.forEach(el => {
if (el instanceof HTMLButtonElement) {
el.disabled = true;
}
});
The safest and most idiomatic pattern: check for null with an early return, then narrow with instanceof to access specific properties. This pattern handles both the "element doesn't exist" case and the "wrong element type" case, giving you full type safety with zero runtime risk.
DOM typing in TypeScript has several gotchas that can lead to runtime errors if you're not careful. Let's walk through the most common ones.
Trap 1: Abusing the Non-Null Assertion Operator (!)
// This compiles fine but crashes at runtime
// if the element doesn't exist:
document.getElementById("app")!
.innerHTML = "Hello";
// CORRECT: Check for null first
const app =
document.getElementById("app");
if (app) {
app.innerHTML = "Hello";
}
Trap 2: Forgetting that querySelector Returns Element, not HTMLElement
const el =
document.querySelector("div");
// Type: Element | null
// Element doesn't have .style or .innerHTML
// without narrowing!
// NARROW first:
if (el instanceof HTMLElement) {
el.style.color = "red"; // ✅ Now works
}
Trap 3: Assuming querySelector with Tag Name Gives Specific Type
const btn =
document.querySelector("button");
// Type: Element | null
// NOT HTMLButtonElement | null!
// TypeScript can't guarantee the selector
// matches at runtime, so it returns Element.
// Fix:
if (btn instanceof HTMLButtonElement) {
btn.disabled = true;
}
Trap 4: SVG Elements Don't Extend HTMLElement
SVG elements extend SVGElement (which extends Element), not HTMLElement. Trying to use instanceof HTMLElement on SVG elements will fail:
const svg =
document.querySelector("svg");
// Type: Element | null
if (svg instanceof SVGSVGElement) {
// SVG-specific properties!
svg.viewBox.baseVal;
}
Trap 5: Creating Elements with createElement
const div =
document.createElement("div");
// Type: HTMLDivElement ✅
// But unknown tag names:
const custom =
document.createElement("my-component");
// Type: HTMLElement (generic)Here's your complete cheatsheet for typing DOM elements in TypeScript. Pin this to your mental board!
Selection Methods and Their Return Types:
getElementById(id) // HTMLElement | null
querySelector(sel) // Element | null
querySelectorAll(sel) // NodeListOf<Element>
getElementsByClassName() // HTMLCollection
createElement(tag) // Specific HTMLElement
Safe Access Pattern:
const el = document.getElementById("input");
if (!el) return; // null check
if (el instanceof HTMLInputElement) {
el.value; // ✅ Safe!
}
Common Element Types:
HTMLDivElement, HTMLInputElement,
HTMLButtonElement, HTMLAnchorElement,
HTMLImageElement, HTMLFormElement,
HTMLSelectElement, HTMLTextAreaElement,
HTMLParagraphElement, HTMLSpanElement,
SVGSVGElement // for SVG elements
Key Rules:
- Always null-check DOM elements — getElementById and querySelector can return null
- Use instanceof to narrow to specific element types for property access
- querySelector returns Element | null, not HTMLElement | null
- SVG elements extend SVGElement, not HTMLElement — use SVGSVGElement etc.
- Prefer getElementById or tag-specific selectors when possible
- Avoid the non-null assertion (!) — it bypasses safety
The Golden Rule: "DOM element typing is like choosing the right CCTV lens at an RTA checkpost. HTMLElement is the wide-angle lens — it shows you something is there. instanceof HTMLInputElement is the zoom lens — it reveals the details. Use the right lens for the right job, bhai!"
Key Takeaways
- getElementById returns HTMLElement | null — always check for null first
- querySelector returns Element | null — narrower than HTMLElement
- Use instanceof to narrow to specific element types like HTMLInputElement
- Each HTML element has a corresponding TypeScript interface in lib.dom.d.ts
- SVG elements extend SVGElement, not HTMLElement — use instanceof SVGSVGElement
- Avoid the non-null assertion (!) — it bypasses TypeScript safety checks
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login