completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Name the kinds of nodeTell elements, text, comments, the document, and the doctype apart, and check each one with nodeType and nodeName.
  • 02
    Read the treeFind a node's parent, children, and siblings, and know why childNodes can hold spaces and comments that children leaves out.
  • 03
    Trust 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.

The shape of every HTML pageText
document├─ <!DOCTYPE html>└─ HTML   ├─ HEAD   └─ BODY

Read this sketch from the top down:

  1. Line 1 is document, the root.
  2. Line 2 is the doctype, <!DOCTYPE html>. It sits right under document.
  3. Line 3 is the <html> element. It also sits right under document.
  4. Lines 4 and 5 are <head> and <body>. They sit inside <html>.
A tree of cards. At the top is document, the root. Arrows go down from document to two children: the doctype and html. Arrows go down from html to its two children: head and body. Head and body are siblings.documentroot<!DOCTYPE html><html>parent<head><body>head and body are siblings
Each arrow points from a parent to its child. 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.documentElement is the <html> element.
  • document.head is the <head> element.
  • document.body is the <body> element.
Read the three main elementsPop out in the code editor (opens in a new tab)JavaScript
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.

Real-life analogyThe DOM is like a family tree

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 of document
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

SORT

Everything 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>,
  • document itself.

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.

Inspect one elementPop out in the code editor (opens in a new tab)JavaScript
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"));
  1. Line 1 finds the list. document.querySelector("ul") returns the first <ul> on the page.
  2. Line 2 gets the list's first element child, the first <li>.
  3. Line 3 prints 1. nodeType is a number that says what kind of node this is, and 1 means "element".
  4. Line 4 prints LI. In an HTML page, nodeName gives an element's tag name in capital letters.
  5. Line 5 prints li. localName gives the same tag name in small letters.
  6. 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.

The common kinds of DOM node
Kind of nodenodeTypenodeNameExample
Element1The tag name in capital letters, like P. (localName gives p.)<p>
Text3#textThe words inside an element, or the spaces between tags.
Comment8#comment<!-- note -->
Document9#documentdocument
Doctype10The doctype's name, usually html.<!DOCTYPE html>
Document fragment11#document-fragmentA 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.

Spaces between tags are nodes tooPop out in the code editor (opens in a new tab)JavaScript
const list = document.querySelector("ul");
console.log(list.childNodes.length);
console.log(list.children.length);
console.log(list.childNodes[0].nodeName);
  1. Line 1 finds the list again.
  2. Line 2 counts every child node with childNodes. It prints 7.
  3. Line 3 counts only the element children with children. It prints 2.
  4. Line 4 asks for the name of the very first child node. It prints #text: the line break and spaces before the first <li>.
The list's childNodes in a row: text, li, text, li, text, comment, text. That is 7 nodes. Arrows go down from the two li cards to a second row, list.children, which holds only the 2 li elements.list.childNodes.length is 7#text<li>#text<li>#text#comment#text<li><li>list.children.length is 2text and comment nodes are left out
The spaces between the tags and the comment are nodes too, so 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.

Is it a node?
  • <p>
  • the words inside it
  • <!-- note -->
  • <!DOCTYPE html>
  • document
  • the line break between two tags
  • class="x"
  • <br>
Try it yourself
0 of 8 correct

Put each card in its group: a DOM node, or not a child node.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Text, comments, and whitespace

STEP THROUGH

The 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.

Real-life analogyBoxes inside boxes

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.

childNodes and children side by side
QuestionchildNodeschildren
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 childNodes and children
Step 0 of 3Ready
Your turn: follow the blue line

Step through why a parent can have more child nodes than child elements.

Running in
  1. script
Next: line 1
  childNodes: ["#text", "P", "#comment", "BR"],  children: ["P", "BR"],};console.log(box.childNodes.length);console.log(box.children.length);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

HTML → DOM tree viewer

INTERACTIVE

Now 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:

  • DOMParser does 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 the tree
Step 0 of 4Ready
Your turn: follow the blue line

Step through the first rows of a small page's DOM tree.

Running in
  1. script
Next: line 1
console.log(labels[0]);console.log(labels[1]);console.log(labels[2]);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
HTML → DOM tree viewer
How the viewer parses your HTMLJavaScript
const parsed = new DOMParser().parseFromString(html, "text/html");const plainTree = convertDomNode(parsed);renderRows(treeToRows(plainTree, { hideWhitespace }));
The DOM tree the parser builtSpaces shown
Try it yourself

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.

DevTools often hides text nodes that are only spaces. Use the first button to see the tree both ways.

Autocorrected HTML

CHALLENGE

The 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.
Real-life analogyA teacher who quietly fixes your essay

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>
Fixes we saw the parser make
  • 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.

See the tbody the parser addsPop out in the code editor (opens in a new tab)JavaScript
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);
  1. Lines 1 to 4 parse the HTML string into a new DOM tree called parsed.
  2. Line 5 finds the table and prints the name of its first element child. It prints TBODY, not TR. The parser put a <tbody> between the table and the row.
  3. Line 6 finds that <tbody> and prints the name of its first element child. It prints TR: the row now sits inside the <tbody>.
Top row, what you typed: table, then tr, then td. Bottom row, what the browser built: table, then tbody, then tr, then td. The tbody was added by the browser.What you typed<table><tr><td>What the browser built<table><tbody><tr><td>added by the browser
Each arrow points from a parent to its child. You typed <tr> right inside <table>, but the browser put a <tbody> between them.
Autocorrect challenge
What the Show button runsJavaScript
const parsed = new DOMParser().parseFromString(snippet, "text/html");const tree = convertDomNode(parsed);const html = parsed.documentElement.outerHTML;
<table><tr><td>A</td></tr></table>
What the browser builtWaiting

Pick a guess, then show the tree the parser really built.

Try it yourself

Pick the result you expect, then click Show the real DOM. Your browser parses the HTML right now to get the answer.

Parsed in this browser with DOMParser. Other tools may print quotes or letter case a little differently, but the tree is the same.

Exploring in DevTools

TRY IT

The 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.
  1. Open DevTools, then open the Elements or Inspector panel.
  2. 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.
  3. Open the Console. In most browsers, $0 means "the node selected in the Elements panel right now". Try $0.nodeName and $0.localName.
  4. Compare $0.childNodes with $0.children. Click each result to open it.
  5. 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.
  6. Change some HTML in the Elements panel. You are changing the DOM tree in this tab. The file on the server stays the same.
Try it on this page

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.

Read the names in a listPop out in the code editor (opens in a new tab)JavaScript
const list = document.querySelector("ul");
const names = [...list.children].map((item) => item.textContent.trim());
console.log(names.join(", "));
  1. Line 1 finds the list.
  2. Line 2 makes an array from the list's element children with [...list.children]. Then .map() turns each item into its text. textContent is the text inside an element, and .trim() removes spaces at the start and end.
  3. 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 children when you only want elements. This is most of the time.
  • Use childNodes when 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 document show 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.

Is the body there yet?

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 document are 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 EXERCISES
Exercise 1 · Warm-upSpot the hidden node

Between two list items there is a line break and some spaces. What kind of node does that whitespace become?

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict the two counts

    Read the program without running it. Which two numbers does it print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const childNodes = ["#text", "P", "#comment", "BR"];
    const children = ["P", "BR"];
    console.log(childNodes.length);
    console.log(children.length);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeProve that comments are nodes

      A teammate says comments are not in the DOM. Which nodeType number shows that a comment is a node?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticePredict the parser's fix

        Which element does the browser add around the row?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        <table><tr><td>Total</td></tr></table>

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeInspect this lesson page

          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.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          // Select an element in the Elements panel, then try these in the Console:
          $0
          $0.nodeName
          $0.childNodes
          $0.children
            Exercise 6 · PracticeChoose the right list

            A menu should go through only its <li> elements. Which list should it loop over: childNodes or children?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              8 QUESTIONS
              The DOM tree quiz · 8 questionsScore: first tries count
              1. Question 1 of 8Which object is the root of the DOM tree for the page?

                Choose an answer to see the explanation.

              2. 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 predictPop out in the code editor (opens in a new tab)JavaScript
                console.log(1);
                console.log(3);
                console.log(8);

                Choose an answer to see the explanation.

              3. Question 3 of 8Which list holds text nodes made of spaces, and comments too?

                Choose an answer to see the explanation.

              4. Question 4 of 8In an HTML page, what is the nodeName of a <p> element?

                Choose an answer to see the explanation.

              5. Question 5 of 8You write table rows right inside <table>. What does the browser add?

                Choose an answer to see the explanation.

              6. Question 6 of 8A script in <head> runs before the parser reaches the body. What is document.body at that moment?

                Choose an answer to see the explanation.

              7. Question 7 of 8Which sentence about attributes is true?

                Choose an answer to see the explanation.

              8. Question 8 of 8What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const 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 document are nodes too.
              • childNodes holds every child node. children holds only the child elements.
              • The HTML parser fixes many mistakes, so the DOM can be different from the HTML you typed.
              • In DevTools, $0 is 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.

              CompleteFrontend Clear concepts. Working examples.