TypeScript: DOM Object types
DOM Object Types in TypeScript
explain the types of the document object model for coding with typescript perhaps show a hierarchy of dom types
ai answer
TypeScript’s DOM types live in lib.dom.d.ts (included when "lib" contains "dom"). They mirror the browser’s interface inheritance, so a typed value is both a tree node and a more specific HTML/SVG/MathML object.
Core hierarchy
EventTarget
├── Window
└── Node
├── Document (document)
│ └── XMLDocument
├── DocumentType (<!DOCTYPE html>)
├── DocumentFragment
│ └── ShadowRoot
├── Attr (element attributes; not children of the element)
├── CharacterData
│ ├── Text
│ ├── Comment
│ ├── CDATASection
│ └── ProcessingInstruction
└── Element (any namespaced tag)
├── HTMLElement (HTML namespace)
│ ├── HTMLDivElement
│ ├── HTMLInputElement
│ ├── HTMLAnchorElement
│ ├── HTMLCanvasElement
│ ├── HTMLFormElement
│ └── … (one interface per specialized tag)
├── SVGElement
│ ├── SVGSVGElement
│ ├── SVGPathElement
│ └── …
└── MathMLElement
Window and Document also implement EventTarget. Mixins such as ParentNode, ChildNode, NonDocumentTypeChildNode, Slottable, and ARIAMixin are folded into these interfaces in TypeScript rather than shown as extra classes.
What each layer adds:
| Type | Role | Typical APIs |
|---|---|---|
EventTarget |
Can receive events | addEventListener, dispatchEvent |
Node |
Tree structure | parentNode, childNodes, appendChild, nodeType, textContent |
Element |
A tag (HTML, SVG, or MathML) | id, classList, getAttribute, querySelector, innerHTML |
HTMLElement |
HTML-specific element | style, innerText, dataset, hidden, offsetWidth |
| Concrete types | Tag-specific | HTMLInputElement.value, HTMLAnchorElement.href, HTMLCanvasElement.getContext |
Node.nodeType distinguishes kinds at runtime (ELEMENT_NODE = 1, TEXT_NODE = 3, DOCUMENT_NODE = 9, etc.).
Why Element ≠ HTMLElement
querySelector / querySelectorAll return Element (or NodeListOf<Element>) because a selector can match HTML, SVG, or MathML. HTMLElement is only for HTML, so .style and .innerText are not on Element.
const el = document.querySelector(".item"); // ^? Element | null el?.innerText; // error: Property 'innerText' does not exist on type 'Element'
Narrow or assert when you know it is HTML:
const el = document.querySelector<HTMLElement>(".item"); const input = document.querySelector("input"); // HTMLInputElement | null
getElementById is typed as HTMLElement | null (HTML-centric). createElement("div") is HTMLDivElement because of HTMLElementTagNameMap.
The maps TypeScript uses
interface HTMLElementTagNameMap { a: HTMLAnchorElement; div: HTMLDivElement; input: HTMLInputElement; canvas: HTMLCanvasElement; // … } document.createElement("input"); // HTMLInputElement document.querySelector("canvas"); // HTMLCanvasElement | null document.querySelectorAll("a"); // NodeListOf<HTMLAnchorElement>
Unknown / custom tags fall back to HTMLElement (or Element for querySelector with a free-form string). You can extend the map for custom elements:
declare global { interface HTMLElementTagNameMap { "my-widget": MyWidgetElement; } }
There is a matching SVGElementTagNameMap.
Collections
childNodes→NodeListOf<ChildNode>(elements, text, comments)children→HTMLCollectionofElementonlyquerySelectorAll→NodeListOf<…>(static-ish list of elements)
HTMLCollection and NodeList are live or snapshot depending on the API; they are not arrays, though they are iterable.
Practical typing patterns
// Prefer the tag map when the selector is a tag name const form = document.querySelector("form"); // HTMLFormElement | null // Generic when the selector is not a tag const field = document.querySelector<HTMLInputElement>("#email"); // Events: target is EventTarget; currentTarget is the listener’s element button.addEventListener("click", (e: MouseEvent) => { const t = e.currentTarget; // HTMLButtonElement if listener is on the button }); // Guard instead of asserting function isHTMLElement(n: EventTarget | null): n is HTMLElement { return n instanceof HTMLElement; }
instanceof HTMLInputElement (and friends) is the usual runtime check and also narrows the TypeScript type.
Mental model
- Everything you attach listeners to is an
EventTarget. - Everything in the document tree is a
Node. - Tags are
Element; HTML tags areHTMLElement. - Specific tags get their own interface so properties like
valueorhrefare typed. - TypeScript encodes that last step with
HTMLElementTagNameMap/SVGElementTagNameMap.
If you treat HTMLElement as “any HTML node I can style and listen on,” and only drop to a concrete type (HTMLInputElement, HTMLCanvasElement, …) when you need tag-specific members, the types stay accurate and the compiler will catch most misuse.