TypeScript: DOM Object types

By Xah Lee. Date: .

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 ElementHTMLElement

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

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

  1. Everything you attach listeners to is an EventTarget.
  2. Everything in the document tree is a Node.
  3. Tags are Element; HTML tags are HTMLElement.
  4. Specific tags get their own interface so properties like value or href are typed.
  5. 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.