What is the DOM?

The Document Object Model (DOM) is a programming interface that browsers create when they load an HTML page. It represents your entire HTML document as a structured tree of objects — each tag, attribute, and piece of text becomes a node in this tree.

Think of it this way: HTML is the blueprint, but the DOM is the actual building the browser constructs from that blueprint. Once the building exists, JavaScript can walk into any room, rearrange the furniture, add new rooms, or tear down walls — all in real time, without reloading the page.

The DOM is not part of the JavaScript language itself. It's a Web API provided by the browser. JavaScript simply uses this API to interact with your page. In theory, any programming language could use the DOM — JavaScript just happens to be the one browsers support natively.

How the DOM Tree Works

When the browser parses an HTML document, it builds a tree structure where every piece of content is represented as a node. This tree has a single root (the document object) and branches out into the <html>, <head>, <body>, and all their descendants.

HTML → DOM Tree
<!DOCTYPE html> <html lang="en"> <head> <title>My Page</title> </head> <body> <h1>Hello!</h1> <p>Welcome to the DOM.</p> </body> </html>

The browser converts this into a tree:

document
└── html (lang="en")
    ├── head
    │   └── title
    │       └── "My Page"  (text node)
    └── body
        ├── h1
        │   └── "Hello!"  (text node)
        └── p
            └── "Welcome to the DOM."  (text node)

Key insight: Whitespace between tags (spaces, newlines, indentation) also creates Text nodes in the DOM. This is why childNodes often returns more nodes than you expect — it includes these invisible whitespace text nodes.

Types of DOM Nodes

Not everything in the DOM tree is an HTML element. The DOM distinguishes between several types of nodes, each with a numeric nodeType identifier:

Node TypenodeTypeDescription
Document9The root of the entire DOM tree. Represents the whole HTML or XML document. Accessed via the global document object.
Element1Represents an HTML tag like <div>, <p>, or <a>. Element nodes can have attributes and child nodes.
Text3The actual text content inside an element. For example, the words inside a <p> tag are a Text node.
Comment8Represents an HTML comment (<!-- ... -->). Comment nodes exist in the tree but are not rendered.
DocumentFragment11A lightweight container that holds a group of nodes. Useful for building subtrees in memory before inserting them into the live DOM.
Attr (Attribute)2Represents an attribute of an element (e.g. class="active"). In modern DOM, attributes are accessed via element methods rather than as separate nodes.

Selecting Elements

Before you can change anything in the DOM, you first need to find it. These methods let you reach into the tree and grab one or more elements using IDs, CSS selectors, class names, or tag names.

Method / PropertyDescriptionReturns
document.getElementById(id)Returns the single element with the matching id attribute. Returns null if no match is found.Element | null
document.querySelector(selector)Returns the first element matching a CSS selector. Extremely versatile — supports any valid CSS selector.Element | null
document.querySelectorAll(selector)Returns all elements matching a CSS selector as a static NodeList. Does not update when the document changes.NodeList
document.getElementsByClassName(name)Returns a live HTMLCollection of all elements with the given class name. Updates automatically when the DOM changes.HTMLCollection
document.getElementsByTagName(tag)Returns a live HTMLCollection of all elements with the given tag name (e.g. "div", "p").HTMLCollection

Creating & Inserting

The DOM isn't read-only — you can build brand-new elements in JavaScript and inject them into the page. Elements created with createElement() exist only in memory until you explicitly attach them to the document tree.

Method / PropertyDescriptionReturns
document.createElement(tagName)Creates a new HTML element in memory. The element is not part of the document until you insert it.Element
document.createTextNode(text)Creates a new Text node with the given string content.Text
parent.appendChild(child)Appends a node as the last child of a parent element. If the child already exists elsewhere, it is moved.Node
parent.insertBefore(newNode, refNode)Inserts a node before a reference child node. If refNode is null, the new node is appended at the end.Node
element.append(...nodes)Inserts nodes or strings after the last child. Unlike appendChild, it accepts multiple arguments and strings.void
element.prepend(...nodes)Inserts nodes or strings before the first child of the element.void
element.after(...nodes)Inserts nodes or strings immediately after the element itself (as siblings).void
element.before(...nodes)Inserts nodes or strings immediately before the element itself (as siblings).void

Modifying & Removing

Once elements are in the DOM, you can replace them, duplicate them, or remove them entirely. Removed nodes aren't destroyed immediately — they still exist in memory and can be re-inserted later if needed.

Method / PropertyDescriptionReturns
element.remove()Removes the element from the DOM entirely. Simple and modern — no need to reference the parent.void
parent.removeChild(child)Removes a child node from its parent. Returns the removed node, which still exists in memory.Node
parent.replaceChild(newChild, oldChild)Replaces an existing child node with a new one. The old child is returned.Node
element.replaceWith(...nodes)Replaces the element with a set of nodes or strings. The element is removed from the tree.void
element.cloneNode(deep)Creates a copy of the element. If deep is true, all descendants are also cloned.Node

Attributes & Properties

HTML attributes (like class, id, data-*) and DOM properties (like classList, dataset) are how you inspect and configure elements. Attributes live in the HTML markup; properties are the JavaScript interface to those same values.

Method / PropertyDescriptionReturns
element.getAttribute(name)Returns the value of the specified attribute, or null if it doesn't exist.string | null
element.setAttribute(name, value)Sets the value of an attribute on the element. Creates the attribute if it doesn't already exist.void
element.removeAttribute(name)Removes the specified attribute from the element.void
element.hasAttribute(name)Returns true if the element has the specified attribute, false otherwise.boolean
element.classListReturns a DOMTokenList for the class attribute. Provides add(), remove(), toggle(), contains(), and replace() methods.DOMTokenList
element.datasetReturns a DOMStringMap of all data-* attributes. Access data-user-id as element.dataset.userId.DOMStringMap

Content & Styling

These properties let you read or change what's inside an element — from plain text to full HTML markup — and modify its visual appearance directly through inline styles or by reading the browser's computed values.

Method / PropertyDescriptionReturns
element.textContentGets or sets the text content of an element and all its descendants. Does not parse HTML.string
element.innerHTMLGets or sets the HTML markup inside the element. Parses the string as HTML. Caution: can introduce XSS vulnerabilities.string
element.outerHTMLGets or sets the HTML including the element itself. Setting it replaces the element entirely.string
element.styleProvides direct access to the inline CSS styles of the element (e.g. element.style.color = "red").CSSStyleDeclaration
getComputedStyle(element)Returns the final computed CSS values of all properties on the element, after all stylesheets have been applied.CSSStyleDeclaration

DOM Traversal

Once you've selected an element, you can navigate to its relatives — parents, children, and siblings — using these properties:

Property / MethodDescription
parentNodeReturns the parent of the current node. For the document node, this is null.
parentElementReturns the parent element. Unlike parentNode, returns null if the parent is not an Element (e.g. Document).
childNodesReturns a live NodeList of all child nodes, including text and comment nodes.
childrenReturns a live HTMLCollection of only the child elements (no text or comment nodes).
firstChildReturns the first child node (could be text or whitespace). Use firstElementChild for the first element.
firstElementChildReturns the first child that is an Element node, skipping text and comments.
lastChildReturns the last child node. Use lastElementChild for the last element.
lastElementChildReturns the last child that is an Element node.
nextSiblingReturns the next node at the same level (could be text). Use nextElementSibling for the next element.
nextElementSiblingReturns the next sibling that is an Element, skipping text and comment nodes.
previousSiblingReturns the previous node at the same level. Use previousElementSibling for the previous element.
previousElementSiblingReturns the previous sibling that is an Element.
closest(selector)Walks up the DOM tree and returns the first ancestor (or self) matching the CSS selector. Returns null if none found.

DOM Events System

Events are signals that something has happened — a click, a key press, the page finishing loading, or a form being submitted. The DOM event system lets you listen for and respond to these signals.

ConceptDescription
addEventListener()The modern way to attach event handlers. Supports multiple listeners per event, capture/bubble phase control, and options like once and passive.
removeEventListener()Removes an event listener previously registered with addEventListener. Requires the exact same function reference.
Event BubblingWhen an event occurs on an element, it first runs handlers on that element, then on its parent, then upward through the ancestors. Most events bubble by default.
Event CapturingThe opposite of bubbling — the event travels from the root down to the target. Enable capture by passing { capture: true } to addEventListener.
event.stopPropagation()Prevents the event from bubbling up (or capturing down) to parent elements. Only the current listener runs.
event.preventDefault()Prevents the browser's default behavior for the event (e.g. following a link, submitting a form, scrolling).
Event DelegationInstead of attaching listeners to many child elements, attach a single listener to a common ancestor and use event.target to identify which child was clicked. More efficient and works for dynamically added elements.

Practical Examples

Selecting and Modifying Elements
// Select an element by ID const heading = document.getElementById("main-title"); // Change its text heading.textContent = "Updated Title!"; // Change its style heading.style.color = "#6c5ce7"; heading.style.fontSize = "2.5rem"; // Add a CSS class heading.classList.add("highlighted"); // Select all paragraphs and change them const paragraphs = document.querySelectorAll("p"); paragraphs.forEach(p => { p.style.lineHeight = "1.8"; });
Creating and Inserting Elements
// Create a new card element const card = document.createElement("div"); card.className = "card"; // Create heading inside the card const title = document.createElement("h3"); title.textContent = "New Feature!"; // Create description const desc = document.createElement("p"); desc.textContent = "This card was built entirely with JavaScript."; // Assemble the card card.appendChild(title); card.appendChild(desc); // Insert it into the page document.getElementById("container").appendChild(card);
Event Delegation Pattern
// Instead of adding a listener to every button... // Add ONE listener to the parent container const nav = document.getElementById("main-nav"); nav.addEventListener("click", (event) => { // Check if the clicked target is a button if (event.target.matches("button.nav-item")) { // Remove active from all buttons nav.querySelectorAll("button.nav-item").forEach(btn => { btn.classList.remove("active"); }); // Mark the clicked one as active event.target.classList.add("active"); console.log("Navigated to:", event.target.dataset.page); } }); // This works even for buttons added AFTER the listener was attached!
DocumentFragment — Batch DOM Updates
// Build 100 list items WITHOUT triggering 100 reflows const fragment = document.createDocumentFragment(); for (let i = 1; i <= 100; i++) { const li = document.createElement("li"); li.textContent = `Item #${i}`; fragment.appendChild(li); // append to the fragment (in memory) } // Single DOM insertion — only ONE reflow/repaint document.getElementById("my-list").appendChild(fragment);
Using DocumentFragment avoids triggering layout recalculations on every insert — the browser only processes the change once when the fragment is appended.

NodeList vs HTMLCollection

DOM methods return different types of collections. Understanding the difference prevents subtle bugs:

FeatureNodeListHTMLCollection
Returned byquerySelectorAll(), childNodesgetElementsByClassName(), getElementsByTagName(), children
Live or Static?Static (from querySelectorAll) — a snapshot that doesn't updateLive — automatically updates when the DOM changes
ContainsAny node type (elements, text, comments)Only Element nodes
forEach support✅ Yes, has .forEach()❌ No — convert with Array.from() or [...collection]

Performance Best Practices

Every time you modify the DOM, the browser may need to recalculate layouts (reflow) and repaint pixels on screen. These operations are expensive. Here's how to minimize their cost:

  • Batch DOM changes: Use DocumentFragment or build an HTML string and set innerHTML once, instead of inserting elements one by one.
  • Cache selectors: Store the result of querySelector() in a variable instead of querying the DOM repeatedly in a loop.
  • Use event delegation: Attach one listener to a parent instead of attaching listeners to hundreds of child elements.
  • Prefer classList over className: Using classList.add() and classList.remove() is more efficient and safer than overwriting className directly.
  • Avoid layout thrashing: Don't alternate between reading layout properties (like offsetHeight) and writing styles. Group all reads together, then all writes.
  • Use textContent over innerHTML: When you only need to set text, textContent is faster and avoids HTML parsing and XSS risks.
  • Detach, modify, reattach: For complex modifications, remove the element from the DOM, make all changes, then reinsert it — triggering only one reflow.

DOM vs Virtual DOM

Modern JavaScript frameworks like React, Vue, and Preact use a concept called the Virtual DOM — a lightweight JavaScript copy of the real DOM tree. When your data changes, the framework first updates this virtual copy, then calculates the minimum number of changes needed, and finally applies only those changes to the real DOM.

This is called reconciliation or diffing. The benefit is that instead of replacing large chunks of the page, the framework surgically updates only what actually changed — making complex UIs fast and efficient.

Understanding the real DOM is essential even when using these frameworks, because the Virtual DOM is just an optimization layer on top of the same browser APIs covered on this page.

Elements in the DOM also support global attributes and event handler attributes. Browse our HTML Tags Reference to learn about individual elements.