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 kinds of nodeTell elements, text, comments, the document, and the doctype apart, and check each one with
nodeTypeandnodeName. - 02Read the treeFind a node's parent, children, and siblings, and know why
childNodescan hold spaces and comments thatchildrenleaves out. - 03Trust what the browser builtExplain why the DOM can be different from the HTML you typed, and use DevTools to look at the tree the browser really built.
A page becomes a tree
A web page starts as HTML text. The text comes from a file or from a server. On its own, it is just letters and angle brackets.
The browser reads this text with its HTML parser. A parser is a program that reads text and works out its structure. From the text, the parser builds the DOM. DOM is short for Document Object Model.
The DOM is:
- a set of objects, one for each part of the page,
- arranged as a tree, where each object can hold other objects,
- live, so JavaScript can read it and change it while the page is open.
Each object in the tree is called a node. The node at the very top is called the root. On a web page, the root is document.
document├─ <!DOCTYPE html>└─ HTML ├─ HEAD └─ BODYRead this sketch from the top down:
- Line 1 is
document, the root. - Line 2 is the doctype,
<!DOCTYPE html>. It sits right underdocument. - Line 3 is the
<html>element. It also sits right underdocument. - Lines 4 and 5 are
<head>and<body>. They sit inside<html>.
document is the root, and head and body are siblings because they share one parent, html.You don't have to search for these three elements. document has a property for each one:
document.documentElementis the<html>element.document.headis the<head>element.document.bodyis the<body>element.
console.log(document.documentElement.nodeName);
console.log(document.head.nodeName);
console.log(document.body.nodeName);nodeName is a property that gives a node's name. For an HTML element, the name is the tag name in capital letters. So line 1 prints HTML, line 2 prints HEAD, and line 3 prints BODY.
One warning about timing. The parser reads the page from top to bottom. A script inside <head> can run before the parser reaches <body>. At that moment, document.body is null, because the body doesn't exist yet.
In a family, everyone has a place: parent, child, brother or sister, grandparent. The DOM uses the same words. A node that holds another node is its parent. The node inside is its child. Nodes with the same parent are siblings. All the nodes above a node are its ancestors, and all the nodes below it are its descendants.
The next lesson, Walking & searching the DOM, uses these words to move around the tree.
- In real life: Oldest ancestor
- In JavaScript:
document, the root node - In real life: Child
- In JavaScript:
<html>is a child ofdocument - In real life: Parent and children
- In JavaScript:
<body>is the parent of the headings, paragraphs, lists, and text inside it - In real life: Siblings
- In JavaScript: Two
<li>elements in the same<ul>
Nodes and elements
SORTEverything in the DOM tree is a node. An element is one kind of node. It comes from a tag, such as <p>, <button>, or <br>.
Some nodes are not elements. These are nodes too:
- the text between tags,
- comments, such as
<!-- note -->, - the doctype,
<!DOCTYPE html>, documentitself.
The next examples use a small list. When you run them here, the page holds a <ul> with two <li> items, Home and Docs, and a comment after them. Each item has a data-kind attribute.
const list = document.querySelector("ul");
const firstItem = list.firstElementChild;
console.log(firstItem.nodeType);
console.log(firstItem.nodeName);
console.log(firstItem.localName);
console.log(firstItem.hasAttribute("data-kind"));- Line 1 finds the list.
document.querySelector("ul")returns the first<ul>on the page. - Line 2 gets the list's first element child, the first
<li>. - Line 3 prints
1.nodeTypeis a number that says what kind of node this is, and1means "element". - Line 4 prints
LI. In an HTML page,nodeNamegives an element's tag name in capital letters. - Line 5 prints
li.localNamegives the same tag name in small letters. - Line 6 prints
true.hasAttribute()checks whether the element has an attribute with that name. The attribute belongs to the element, but it is not a child node.
The table below lists the common kinds of node. Read one row at a time: the kind of node, the nodeType number it has, the nodeName it has, and an example.
| Kind of node | nodeType | nodeName | Example |
|---|---|---|---|
| Element | 1 | The tag name in capital letters, like P. (localName gives p.) | <p> |
| Text | 3 | #text | The words inside an element, or the spaces between tags. |
| Comment | 8 | #comment | <!-- note --> |
| Document | 9 | #document | document |
| Doctype | 10 | The doctype's name, usually html. | <!DOCTYPE html> |
| Document fragment | 11 | #document-fragment | A holder that JavaScript makes for new nodes. |
The last row needs a few words. A document fragment is a node that JavaScript makes, not the parser. You can build several new elements inside it, then add them all to the page in one step. When you do that, the elements move into the page and the fragment itself stays out. So you won't find a fragment when you look at a page's tree.
Now count the children of the same list. The list in the HTML has spaces and line breaks between its tags, and a comment at the end.
const list = document.querySelector("ul");
console.log(list.childNodes.length);
console.log(list.children.length);
console.log(list.childNodes[0].nodeName);- Line 1 finds the list again.
- Line 2 counts every child node with
childNodes. It prints7. - Line 3 counts only the element children with
children. It prints2. - Line 4 asks for the name of the very first child node. It prints
#text: the line break and spaces before the first<li>.
childNodes has 7 items. children keeps only the 2 <li> elements.Now sort the cards. Be careful with class="x". It is an attribute. You can read it through element.attributes, but it is not a child node in the tree.
<p>- the words inside it
<!-- note --><!DOCTYPE html>document- the line break between two tags
class="x"<br>
Put each card in its group: a DOM node, or not a child node.
Text, comments, and whitespace
STEP THROUGHThe words inside an element are not stored on the element itself. They are a separate node, a text node, and that node is a child of the element.
Spaces and line breaks between tags are called whitespace. Whitespace between tags usually becomes text nodes too. That is why childNodes often holds more items than you expect.
DevTools is the set of tools built into the browser for looking inside a page. Its Elements panel usually hides text nodes that hold only whitespace, so the tree is easier to read. JavaScript still sees them.
When you write <p>Hello</p>, the paragraph element is the box. "Hello" is a note inside the box: a text node.
- In real life: Big outer box
- In JavaScript: A parent element, such as
<main> - In real life: Smaller box inside it
- In JavaScript: An element inside it, such as
<p> - In real life: A note in the smallest box
- In JavaScript: A text node:
#text
The table compares the two lists of children. Read each row as a question, then the answer for childNodes and for children.
| Question | childNodes | children |
|---|---|---|
| What does it hold? | Every child node. | Only the child elements. |
| Can it hold whitespace text nodes? | Yes. | No. |
| Can it hold comments? | Yes. | No. |
| When should you use it? | When you need every node, exactly as it is in the tree. | When you only want the tags inside. |
The step-through below uses a plain object with two arrays. The arrays copy the two lists from a real parent: one text node, a P element, a comment, and a BR element.
Step through why a parent can have more child nodes than child elements.
script
childNodes: ["#text", "P", "#comment", "BR"], children: ["P", "BR"],};console.log(box.childNodes.length);console.log(box.children.length);HTML → DOM tree viewer
INTERACTIVENow look at what the parser really builds. The viewer below takes the HTML you type and parses it in your browser with new DOMParser().parseFromString(html, "text/html"). DOMParser is a built-in tool that turns a string of HTML into a separate DOM tree.
The viewer is safe to play with:
DOMParserdoes not run any scripts in your HTML.- The viewer turns each node into a plain row of text and shows only those rows.
- Your HTML is never put into this lesson page.
Read a tree from the root down. The step-through below puts the first three rows of a small page's tree in a plain array, then prints them in order. It replays recorded example code; it is not the browser's own debugger.
Step through the first rows of a small page's DOM tree.
script
console.log(labels[0]);console.log(labels[1]);console.log(labels[2]);const parsed = new DOMParser().parseFromString(html, "text/html");const plainTree = convertDomNode(parsed);renderRows(treeToRows(plainTree, { hideWhitespace }));Pick an example or type your own HTML. Then click a row in the tree to see that node's details. DOMParser parses your HTML in this browser, and the viewer shows the result only as rows of text.
Autocorrected HTML
CHALLENGEThe HTML parser never stops with an error. When your HTML has a mistake, the parser fixes it and builds a tree anyway. Here are some fixes it makes:
- If
<html>,<head>, or<body>is missing, the parser adds it. - If table rows sit right inside
<table>, the parser wraps them in a<tbody>element.<tbody>means "table body". - If a
<div>appears inside an open paragraph, the parser closes the paragraph before the<div>. - If formatting tags such as
<b>and<i>are closed in the wrong order, the parser rebuilds them into a correct tree. The rules for this have a nickname: the adoption agency algorithm.
Imagine a teacher who fixes your essay before anyone reads it. Everyone reads the fixed copy, not your draft. When you look for a problem, read the fixed copy too: the DOM.
- In real life: Your messy first draft
- In JavaScript: The HTML you typed
- In real life: The teacher's fixed copy
- In JavaScript: The DOM tree the parser built
- In real life: Fixes you did not write
- In JavaScript: Elements the parser added or moved, like
<tbody>
- Rows written right inside
<table>ended up inside a new<tbody>. - A
<div>inside an open<p>closed the paragraph first. Then the later</p>made a new, empty paragraph. - Text written after
</body>ended up inside<body>. <b>and<i>tags closed in the wrong order were rebuilt into a correct tree.
A table is the easiest fix to check. The code parses a table with one row and one cell, then asks what is inside.
const parsed = new DOMParser().parseFromString(
"<table><tr><td>Total</td></tr></table>",
"text/html",
);
console.log(parsed.querySelector("table").firstElementChild.nodeName);
console.log(parsed.querySelector("tbody").firstElementChild.nodeName);- Lines 1 to 4 parse the HTML string into a new DOM tree called
parsed. - Line 5 finds the table and prints the name of its first element child. It prints
TBODY, notTR. The parser put a<tbody>between the table and the row. - Line 6 finds that
<tbody>and prints the name of its first element child. It printsTR: the row now sits inside the<tbody>.
<tr> right inside <table>, but the browser put a <tbody> between them.const parsed = new DOMParser().parseFromString(snippet, "text/html");const tree = convertDomNode(parsed);const html = parsed.documentElement.outerHTML;<table><tr><td>A</td></tr></table>Pick a guess, then show the tree the parser really built.
Pick the result you expect, then click Show the real DOM. Your browser parses the HTML right now to get the answer.
DOMParser. Other tools may print quotes or letter case a little differently, but the tree is the same.Exploring in DevTools
TRY ITThe best way to learn the DOM is to look at a real page. Most browsers have two DevTools panels for this:
- the Elements panel (called Inspector in Firefox), which shows the DOM tree,
- the Console, where you can type JavaScript and see the result.
- Open DevTools, then open the Elements or Inspector panel.
- Choose Inspect, then click a heading, paragraph, or button on this page. When you move the mouse over a node in the panel, the browser shades that part of the page.
- Open the Console. In most browsers,
$0means "the node selected in the Elements panel right now". Try$0.nodeNameand$0.localName. - Compare
$0.childNodeswith$0.children. Click each result to open it. - Right-click a node or a logged value and look for "Store as global variable". Most browsers then give it a short name, such as
temp1, that you can use in the Console. - Change some HTML in the Elements panel. You are changing the DOM tree in this tab. The file on the server stays the same.
Select the heading of this section. In the Console, run $0.nodeName, $0.childNodes, and $0.children. Then select the numbered list above and compare the two lists of children again.
Where you will use this
You need the DOM tree whenever your code has to:
- find an element near another element,
- remove a message, or add a new card,
- read the text on the page,
- work out why a search for an element found the wrong one.
Tools such as React often change the DOM for you. But in the end, the browser still gets a tree of nodes, and you still debug that tree.
const list = document.querySelector("ul");
const names = [...list.children].map((item) => item.textContent.trim());
console.log(names.join(", "));- Line 1 finds the list.
- Line 2 makes an array from the list's element children with
[...list.children]. Then.map()turns each item into its text.textContentis the text inside an element, and.trim()removes spaces at the start and end. - Line 3 joins the names with a comma and prints them. For the small list in this lesson, it prints
Home, Docs.
You can use the same steps to build a menu, a cart, or a to-do list from HTML that is already on the page.
Which list of children should you use?
- Use
childrenwhen you only want elements. This is most of the time. - Use
childNodeswhen text nodes and comments matter too. A text editor or a tool like the viewer above needs this.
When you debug, keep two views apart:
- View Source shows the HTML text the server sent.
- The Elements panel and
documentshow the tree after the parser fixed the HTML and after scripts changed it.
If a search for an element gives a strange result, compare these two views first.
A script in <head> can run before the parser makes <body>. Then document.body is null. There are two simple ways to wait. Add defer to the script tag, or run your code when the DOMContentLoaded event fires. Both run after the whole page has been parsed.
Common misconceptions
- "The DOM is the same as my HTML file." The DOM is the tree the parser built. The parser may have fixed things, and scripts may have changed it since.
- "Every node is an element." Text, comments, the doctype, document fragments, and
documentare nodes too. They are not elements. - "Spaces between tags disappear." They usually become text nodes. The parser drops whitespace only in a few places, such as before
<head>. - "Attributes are children." You can read them through
attributes, but they are not child nodes. - "DevTools shows every node JavaScript can see." The Elements panel may hide text nodes that hold only whitespace, so the tree is easier to read.
- "Broken HTML makes the parser throw an error." The HTML parser does not stop. It fixes many mistakes and still builds a tree.
Practice exercises
6 EXERCISESBetween two list items there is a line break and some spaces. What kind of node does that whitespace become?
A line break between tags is whitespace. In the body of a page, whitespace becomes a text node.
Read the program without running it. Which two numbers does it print?
const childNodes = ["#text", "P", "#comment", "BR"];
const children = ["P", "BR"];
console.log(childNodes.length);
console.log(children.length);The arrays copy the two DOM lists. The first array has 4 items and the second has 2, so the program prints 4 and then 2.
A teammate says comments are not in the DOM. Which nodeType number shows that a comment is a node?
A comment node has nodeType 8 and nodeName #comment. Only nodes have a nodeType, so comments are nodes.
Which element does the browser add around the row?
<table><tr><td>Total</td></tr></table>The parser adds a <tbody>. The row becomes a child of <tbody>, not a child of <table>.
Use DevTools on this page. Select a list. You can store it as a global variable if you like. Then compare its childNodes with its children.
// Select an element in the Elements panel, then try these in the Console:
$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));children holds only the elements. childNodes holds every node. On this page you should see extra text nodes wherever there are spaces or line breaks between tags.
A menu should go through only its <li> elements. Which list should it loop over: childNodes or children?
A menu only needs its <li> elements, so it uses children. A tool that shows the whole tree may need childNodes, so it can see text and comments too.
Check your understanding
8 QUESTIONSQuestion 1 of 8Which object is the root of the DOM tree for the page?
Choose an answer to see the explanation.
Question 2 of 8An element has nodeType 1, a text node has 3, and a comment has 8. What 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 8Which list holds text nodes made of spaces, and comments too?
Choose an answer to see the explanation.
Question 4 of 8In an HTML page, what is the
nodeNameof a<p>element?Choose an answer to see the explanation.
Question 5 of 8You write table rows right inside
<table>. What does the browser add?Choose an answer to see the explanation.
Question 6 of 8A script in
<head>runs before the parser reaches the body. What isdocument.bodyat that moment?Choose an answer to see the explanation.
Question 7 of 8Which sentence about attributes is true?
Choose an answer to see the explanation.
Question 8 of 8What does this print?
Read the code, then predictconst labels = ["Document", "<!DOCTYPE html>", "<html>"]; console.log(labels[0]); console.log(labels[2]);Choose an answer to see the explanation.
Key takeaways
- The DOM is a live tree of nodes that the browser builds from your HTML.
- Elements are nodes. Text, comments, the doctype, document fragments, and
documentare nodes too. childNodesholds every child node.childrenholds only the child elements.- The HTML parser fixes many mistakes, so the DOM can be different from the HTML you typed.
- In DevTools,
$0is the node you selected, so you can inspect it with JavaScript.
One-line definition: the DOM tree is the live tree of objects the browser builds from a page's HTML, after fixing its mistakes.
Up next: Walking & searching the DOM.