The DOM tree
Learn how the browser turns HTML into a DOM tree of document, element, text, comment, and doctype nodes, including whitespace and parser repairs.
- 01Name the node kindsTell elements, text, comments, the document, and the doctype apart.
- 02Read the treeUse
childNodes,children, parents, and siblings without being surprised by whitespace. - 03Trust what the browser builtExplain why the DOM can differ from the HTML you typed.
A page becomes a tree
HTML starts as text: characters in a file or a server response. The browser reads that text with the HTML parser and builds the DOM, the Document Object Model: a live tree of objects JavaScript can inspect and change.
The root object is document. The page’s <html> element is available as document.documentElement, and inside it are document.head and document.body. One timing detail matters later: if a script in <head> runs before the parser reaches the body, document.body can still be null.
document├─ <!DOCTYPE html>└─ HTML ├─ HEAD └─ BODYOnce you see the page as relatives, navigation words become friendly: parent, child, sibling, ancestor, and descendant. The next lesson, Walking & searching the DOM, builds directly on those words.
- In real life: Oldest ancestor
- In JavaScript:
document, the root document node - In real life: Child
- In JavaScript:
<html>is a child of the document - In real life: Parent and children
- In JavaScript:
<body>can contain headings, paragraphs, lists, and text - In real life: Siblings
- In JavaScript: Two
<li>elements in the same<ul>
Where the analogy stops: A real family can have complicated relationships. A DOM node has at most one parent, and moving a node moves that same node; it does not automatically create a copy.
Nodes and elements
SORTEverything in the DOM tree is a node. An element is one kind of node: a tag such as <p>, <button>, or <br>. Text, comments, the document itself, and the doctype are nodes too, but they are not elements.
| Kind | nodeType | Typical nodeName | Example |
|---|---|---|---|
| Element | 1 | Uppercase tag names in HTML documents, like P; localName is lowercase | <p> |
| Text | 3 | #text | The words inside an element, or whitespace between tags |
| Comment | 8 | #comment | <!-- note --> |
| Document | 9 | #document | document |
| Document type | 10 | The doctype name, usually html | <!DOCTYPE html> |
| Document fragment | 11 | #document-fragment | A lightweight container used by DOM APIs |
Try sorting the cards. The trickiest one is class="x": attributes are reachable through element.attributes, but they are not child nodes in the tree.
<p>- the words inside it
<!-- note --><!DOCTYPE html>document- the newline between two tags
class="x"<br>
Decide whether each thing is a DOM node or not a child node.
Text, comments, and whitespace
STEP THROUGHText inside an element is not stored as a magic string on the element. It is a child text node. Whitespace between tags usually becomes text nodes too, which is why childNodes often contains more items than you expected. DevTools’ Elements panel hides whitespace-only text nodes by default in many views.
When you write <p>Hello</p>, the paragraph element is the box and “Hello” is a text node inside it.
- In real life: Outer box
- In JavaScript: A parent element, such as
<main> - In real life: Smaller box inside it
- In JavaScript: A nested element, such as
<p> - In real life: A note in the smallest box
- In JavaScript: A text node:
#text
Where the analogy stops: Boxes make nesting feel concrete, but the DOM also contains nodes that are not boxes, such as comments and the document node.
| Feature | childNodes | children |
|---|---|---|
| Includes | All child nodes | Element children only |
| Can contain whitespace text? | Yes | No |
| Can contain comments? | Yes | No |
| Good for | Exact tree inspection | Working with nested elements |
Step through why a parent can have more child nodes than element children.
script
childNodes: ["#text", "P", "#comment", "BR"], children: ["P", "BR"],};console.log(box.childNodes.length);console.log(box.children.length);HTML → DOM tree viewer
INTERACTIVETime to inspect real parser output. The playground below runs in your browser after the page mounts. It uses new DOMParser().parseFromString(html, "text/html"), converts DOM nodes to a plain data shape, and renders rows as text. It never injects your HTML into the lesson page, and DOMParser does not run scripts from the string.
const parsed = new DOMParser().parseFromString(html, "text/html");const plainTree = convertDomNode(parsed);renderRows(treeToRows(plainTree, { hideWhitespace }));Choose a preset, read the source, then select rows in the tree. The playground uses DOMParser after the page hydrates and renders the result as text rows, never as injected HTML.
Autocorrected HTML
CHALLENGEThe HTML parser is like a teacher who silently fixes your essay before anyone reads it. Missing <html>, <head>, and <body> can be supplied. Rows directly in a table get a <tbody>. A <div> inside an open paragraph closes the paragraph first. Misnested formatting tags are repaired by rules sometimes nicknamed the adoption agency algorithm.
When debugging, inspect the corrected copy: the DOM.
- In real life: Your messy draft
- In JavaScript: The HTML source you typed
- In real life: Teacher’s corrected copy
- In JavaScript: The DOM tree the parser built
- In real life: Red marks you did not write
- In JavaScript: Inserted or moved elements, like
<tbody>
Where the analogy stops: The parser is not trying to understand your intention like a person. It follows detailed HTML rules, and truly invalid structures may still produce surprising trees.
- Rows written directly inside
<table>appeared inside an automatic<tbody>. - A
<div>inside an open<p>closed the paragraph first; the later closing paragraph created an empty paragraph in the tested parse. - Text after
</body>ended up insidebody. - Misnested
<b>and<i>formatting tags were repaired into a valid tree.
const parsed = new DOMParser().parseFromString(snippet, "text/html");const tree = convertDomNode(parsed);const html = parsed.documentElement.outerHTML;<table><tr><td>A</td></tr></table>Choose a prediction, then reveal the parser’s corrected DOM.
Pick the result you expect, then reveal. These results are parsed live in your browser.
DOMParser. The exact serialization can differ in harmless quote or case details across tools, but the tree relationships are the lesson.Exploring in DevTools
TRY ITThe fastest way to build DOM intuition is to inspect a real page. Most browsers have an Elements or Inspector panel and a Console that can talk to the selected node.
- Open DevTools, then open the Elements or Inspector panel.
- Choose Inspect and click a heading, paragraph, or button on this page. Hovering nodes highlights their boxes on the page.
- Open the Console. In most browsers,
$0means “the node currently selected in Elements.” Try$0.nodeNameand$0.localName. - Compare
$0.childNodeswith$0.children. Expand both results. - Right-click a node or logged value and look for “Store as global variable.” Most browsers create a temporary name such as
temp1. - Edit HTML live in the Elements panel. Notice that you are changing the DOM tree, not rewriting the original server file.
Select the heading of this section. In the Console, run $0.nodeName, $0.childNodes, and $0.children. Then select the list above and compare the two collections again.
Where you will use this
DOM tree knowledge shows up whenever code needs to find a nearby element, remove a message, insert a new card, read text, or debug “why did my selector find that?” You will also use it when a framework renders UI: React may hide direct DOM work most days, but the browser still receives a tree.
const list = document.querySelector("ul");
console.log(list.children.length); // element children
console.log(list.childNodes.length); // elements, text, comments...
for (const item of list.children) {
console.log(item.localName, item.textContent.trim());
}In production code, prefer element-focused APIs when you want elements. Reach for node-focused APIs when text nodes and comments matter, such as writing an editor, serializer, or teaching tool like the viewer above.
Common misconceptions
- “The DOM is the same as my source file.” The DOM is the parsed, repaired, live tree.
- “Every node is an element.” Text, comments, the doctype, fragments, and the document are nodes too.
- “Whitespace between tags disappears.” It often becomes text nodes, though the parser drops some whitespace in special places such as before
<head>. - “Attributes are children.” They are reachable through
attributes, but they are not child nodes. - “DevTools shows every node exactly as JavaScript sees it.” DevTools is excellent, but it may hide whitespace-only text nodes for readability.
- “The parser throws an error for broken HTML.” HTML parsing is forgiving; it repairs many mistakes into a DOM tree.
Practice exercises
5 EXERCISESBetween two list items there is a newline and spaces. What kind of node can that whitespace become?
A newline between tags is whitespace text, so it becomes a text node in normal body content.
Run the program mentally. What two numbers are printed?
const childNodes = ["#text", "P", "#comment", "BR"];
const children = ["P", "BR"];
console.log(childNodes.length);
console.log(children.length);The arrays model the DOM collections. The first length is 4 and the second is 2, so the output is 4, 2.
A teammate says comments are not in the DOM. Which nodeType number proves comments are nodes?
A comment node has nodeType 8 and nodeName #comment.
What element will the browser insert around the row?
<table><tr><td>Total</td></tr></table>The parser inserts a tbody, so the row becomes a child of <tbody> instead of a direct child of <table>.
Use DevTools on this page. Select a list, store it as a global variable if you like, and compare its child node collection with its element children.
// Try these in the Console after selecting an element in Elements:
$0
$0.nodeName
$0.childNodes
$0.childrenconst list = document.querySelector("ul");
console.log(list.childNodes.length);
console.log(list.children.length);
console.log([...list.children].map((item) => item.localName));Element-focused code uses children; exact tree inspection uses childNodes. On this page you should see extra text nodes where formatting whitespace exists.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which object is the root of the DOM tree for the current page?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconsole.log(1); console.log(3); console.log(8);Choose an answer to see the explanation.
Question 3 of 7Which collection includes whitespace-only text nodes and comments?
Choose an answer to see the explanation.
Question 4 of 7In an HTML document, what is
nodeNamefor a<p>element?Choose an answer to see the explanation.
Question 5 of 7What does a browser normally insert for rows written directly inside
<table>?Choose an answer to see the explanation.
Question 6 of 7What is true about
document.bodywhile a script in<head>runs before the body is parsed?Choose an answer to see the explanation.
Question 7 of 7Which statement about attributes is accurate?
Choose an answer to see the explanation.
Key takeaways
- The DOM is the browser’s live, parsed tree of nodes.
- Elements are nodes, but text, comments, the doctype, fragments, and the document are nodes too.
childNodesincludes every child node;childrenincludes only element children.- The HTML parser repairs many broken structures, so the DOM can differ from source HTML.
- Use DevTools and
$0to connect what you see with JavaScript objects.
One-line definition: the DOM tree is the browser’s corrected, live object tree for a document.
Up next: Walking & searching the DOM.