Type DOM Elements in TypeScript
Learn how TypeScript types every DOM element so your editor knows exactly which properties and methods are available at each node.
TypeScript DOM elements are typed through a full set of interfaces that mirror the browser's element hierarchy. When you write document.getElementById, the compiler already knows the result is an HTMLElement or null. It does not guess or treat the result as a generic object.
TypeScript ships with type definitions for every DOM element, and it narrows the type automatically based on how you obtain the element. This is what makes TypeScript powerful for DOM manipulation: autocomplete knows which properties exist on a div versus an input, and the compiler flags mistakes like reading .value from a div element before your code ever runs.
The DOM type hierarchy
TypeScript models the DOM with a layered interface hierarchy. Every HTML element type inherits from HTMLElement, which itself inherits from Element and Node. This hierarchy determines which properties and methods are available at each level.
Node provides methods like appendChild and removeChild for basic tree manipulation. Element adds classList, setAttribute, and querySelector for working with attributes and selectors. HTMLElement adds HTML-specific members like style, dataset, click, and focus that you use in everyday interactive code. Each specific element type then adds its own properties: HTMLInputElement has value and checked, HTMLAnchorElement has href, and so on.
Because of this hierarchy, a variable typed as HTMLDivElement has access to every method from all three levels plus any div-specific properties. Understanding this chain helps you know which properties are available without memorising every interface.
How getElementById types the result
The simplest DOM lookup returns a broad type by design. TypeScript cannot know at compile time whether an element with a given id exists in the DOM, or whether it is a div, main, or section. It gives you the safest type: HTMLElement or null.
const container = document.getElementById('app');
// ^? const container: HTMLElement | nullThe null in the return type is important. If the element is not in the DOM, getElementById returns null, and TypeScript forces you to handle that case. The most common pattern is an if check that narrows the type and lets you access HTMLElement properties safely:
const container = document.getElementById('app');
if (container !== null) {
container.style.backgroundColor = 'white'; // OK, narrowed
}Without the check, TypeScript reports that container is possibly null. This guard forces you to write code that handles missing elements gracefully, which is the right thing to do in browser code where the DOM can change at any time.
How createElement gets the right type
When you create an element with document.createElement, TypeScript infers the specific element type from the tag name string. It reads the string you pass, looks it up in a mapping called HTMLElementTagNameMap, and returns the matching interface:
const div = document.createElement('div');
// ^? const div: HTMLDivElement
const input = document.createElement('input');
// ^? const input: HTMLInputElementPass the string "div" and you get HTMLDivElement. Pass "input" and you get HTMLInputElement. The compiler knows exactly which properties exist on each, so accessing .value on a div triggers a compiler error while the same property on an input works fine. This works for every standard HTML tag name.
Narrowing a broad element type
Sometimes you get a broad HTMLElement and need the specific subtype. Use instanceof to narrow the type at runtime. TypeScript understands instanceof checks and narrows the variable's type inside the if block:
const el = document.getElementById('user-input');
if (el instanceof HTMLInputElement) {
console.log(el.value); // OK, narrowed to HTMLInputElement
}The instanceof check tells TypeScript that inside the block, el is HTMLInputElement. The compiler now allows .value and .checked. This pattern is useful when you know what element type to expect but the DOM API returns a broader type.
Using querySelector with a generic
For more precise lookups from the start, pass a generic type argument to querySelector. This tells TypeScript which element type you expect the selector to match:
const nameInput = document.querySelector<HTMLInputElement>('#name');
// ^? const nameInput: HTMLInputElement | null
if (nameInput !== null) {
console.log(nameInput.value); // OK
}The generic narrows the return type from the default Element to HTMLInputElement. You still need the null check because the element might not exist at runtime. The generic only affects the compile-time type, not whether the element is actually in the DOM.
Element vs HTMLElement
The distinction matters when you work with SVG or XML content. TypeScript has separate type hierarchies for HTML and SVG elements that both branch off the same base:
| Type | Extends | Adds |
|---|---|---|
| Element | Node | classList, setAttribute, querySelector |
| HTMLElement | Element | style, dataset, click(), focus() |
| SVGElement | Element | ownerSVGElement, viewportElement |
HTMLInputElement and SVGCircleElement both extend Element, but they are sibling branches with different properties. A function typed to accept HTMLElement will not accept an SVG element without a type assertion. For everyday HTML work, HTMLElement is the type you will use most, since it gives you style, dataset, click, focus, and the other properties that make interactive web pages work.
See Type querySelector in TypeScript for a deeper look at generic selectors and null handling. See Type Event Handlers in TypeScript to learn how to type the events these elements fire.
Rune AI
Key Insights
- Every HTML element maps to a TypeScript interface like HTMLDivElement, HTMLInputElement, or HTMLAnchorElement.
- Methods like getElementById return HTMLElement or null because the compiler cannot know which element will be found.
- Use a generic with querySelector to tell TypeScript which element type you expect.
- Always handle null before accessing element properties.
- The DOM type hierarchy starts at HTMLElement and narrows to specific element types, each adding more precise properties.
Frequently Asked Questions
Why does getElementById return HTMLElement or null?
What is the difference between HTMLElement and Element?
Can I use querySelector with a generic to get the right type?
Conclusion
TypeScript ships with a complete DOM type hierarchy. Every HTML element has its own interface, and the compiler picks the right one based on what you do. Prefer direct element lookups or generic querySelector calls to get precise types, and always handle null.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.