DOM tree

Every HTML page is built out of tags. The browser reads those tags and builds a living model of the page in memory: the Document Object Model, or DOM.

The core idea is small but powerful. Each tag becomes an object. A tag nested inside another becomes a child of that outer object. Even the plain text between tags becomes its own object. String them together and you get a tree of objects that mirrors your markup exactly.

Those objects are not read-only snapshots. JavaScript can reach into any of them and change the page while it runs. Change an object, and the pixels on screen update to match.

Take document.body — the object that stands in for the <body> tag. Run this and the page background flashes red for three seconds, then clears:

document.body.style.background = 'red'; // paint the background red

setTimeout(() => document.body.style.background = '', 3000); // clear it again

The point worth feeling for yourself: touching one property on the body object repaints the real page. Click a swatch below and watch document.body.style.background change the pixels instantly.

interactiveMutate the body object, watch it repaint
HTML text
<body>…</body>
— parse →
DOM tree
node objects
⇄ JS
Rendered
pixels
HTML text is parsed once into a DOM tree of node objects; JavaScript reads and mutates those objects, and the browser repaints.

Here we reached for style.background, but that is one property among many. A few you will meet early:

  • innerHTML — the HTML markup held inside a node, as a string.
  • offsetWidth — the rendered width of the node in pixels.
  • …and a long list beyond those two.

Before we start pulling levers, it pays to understand the shape of the tree they hang off. That structure is the rest of this chapter.

An example of the DOM

Start with a document that has nothing clever in it:

<!DOCTYPE HTML>
<html>
<head>
  <title>About foxes</title>
</head>
<body>
  The truth about foxes.
</body>
</html>

The browser reads that and builds a tree. <html> sits at the root. <head> and <body> hang off it as children. <title> lives inside <head>, and so on down. Here is the whole tree, spaces and line breaks included:

HTML
HEAD
#text “↵␣␣”
TITLE
#text “About foxes”
#text “↵”
#text “↵”
BODY
#text “↵␣␣The truth about foxes.↵”
The DOM for the foxes document. Element nodes are in violet; text nodes show their exact contents, with ↵ for a newline and ␣ for a space.

In a real DOM inspector these element nodes are clickable — expand one and its children fold out, collapse it and they tuck away. The tree stays the tree either way.

Every node in that picture is an object you can hold and inspect from JavaScript.

Two kinds of node dominate here. Tags are element nodes (or just elements), and they are what gives the tree its branches: <html> at the root, <head> and <body> beneath it, and so on.

The text inside an element becomes a text node, drawn with the label #text. A text node holds nothing but a string. It has no children and never will — a text node is always a leaf, the end of a branch. The <title> above, for instance, contains a single text node holding "About foxes".

Whitespace is real

Look closely at the strings in that tree and you will notice two symbols standing in for characters you cannot normally see:

  • — a newline (in JavaScript, \n)
  • — a space
 =  newline  (\n)
 =  space
Two whitespace characters that quietly become text nodes.

Spaces and newlines are ordinary characters, no different from letters or digits as far as the parser cares. When they sit between tags in your source, they become text nodes and join the DOM. That is why <head> above has a #text node before <title> holding a newline and two spaces — the indentation you typed in the file.

There are exactly two exceptions, both at the very top of the document:

  1. Whitespace before <head> is thrown away, for historical reasons baked into the HTML parser.
  2. Anything you place after </body> is silently relocated to the end of <body>. The spec insists all content live inside the body, so trailing whitespace after </body> cannot exist as its own node.

Everywhere else the rule is boringly consistent: whitespace in the source becomes whitespace nodes in the DOM, and deleting it from the source deletes the nodes. Squeeze the same document onto one line and the space-only text nodes vanish entirely:

<!DOCTYPE HTML>
<html><head><title>About foxes</title></head><body>The truth about foxes.</body></html>
HTML
HEAD
TITLE
#text “About foxes”
BODY
#text “The truth about foxes.”
Same document, no whitespace between tags — no whitespace text nodes.

You can watch those phantom nodes appear and vanish. The demo below parses two versions of the same list — one indented, one squeezed onto a line — and counts the child nodes of the <ul>. Edit either string and rerun to see the count follow your whitespace.

interactiveWhitespace between tags becomes text nodes

Autocorrection

Browsers are forgiving parsers. Hand them broken HTML and they patch it up while building the tree, rather than refusing to render.

The root is always <html>. Leave it out of your file and the browser adds it. Same with <body> and <head>. Feed the browser a document that is literally the word Howdy and nothing else, and it wraps that in the full skeleton:

HTML
HEAD
BODY
#text “Howdy”
A one-word document, fully framed by the parser. The head comes out empty, the text lands in body.

The same repair work handles unclosed tags. The parser tracks which elements are open and closes them at the right moments as it reads. Given this sloppy list with no closing tags:

<p>Howdy
<li>Cat
<li>and
<li>Dog

…the browser works out where each element should end and produces a clean tree:

HTML
HEAD
BODY
P
#text “Howdy”
LI
#text “Cat”
LI
#text “and”
LI
#text “Dog”
Unclosed p and li tags, restored into a valid tree by the parser.

Notice the <p> did not swallow the list items — the parser closed it before the first <li>, because a paragraph cannot contain list items. That kind of nesting rule drives every fix-up decision.

Other node types

Elements and text are the two you will handle most, but they are not the only node types.

Comments are nodes too. Here is a list with a comment tucked inside it:

<!DOCTYPE HTML>
<html>
<body>
  The truth about foxes.
  <ol>
    <li>A fox is a smart</li>
    <!-- comment -->
    <li>...and cunning animal!</li>
  </ol>
</body>
</html>
HTML
HEAD
BODY
#text “↵␣␣The truth about foxes.↵␣␣”
OL
#text “↵␣␣␣␣”
LI
#text “A fox is a smart”
#text “↵␣␣␣␣”
#comment “comment”
#text “↵␣␣␣␣”
LI
#text “…and cunning animal!”
#text “↵␣␣”
#text “↵↵↵”
A #comment node (nodeType 8) sitting in the tree between two text nodes, exactly where it appears in the source.

A comment shows up as a #comment node, wedged between the text nodes around it. Why would the browser bother storing something that never renders? Because of one iron rule:

If it is in the HTML, it is in the DOM. Comments included.

That rule reaches further than you might expect. The <!DOCTYPE HTML> directive at the top of the file is a DOM node too, sitting just before <html>. Hardly anyone touches it, and this course never draws it, but it is there. And the document object that represents the entire page? Also a node, formally the root of everything.

The spec defines 12 node types, each with a numeric nodeType. Four of them cover almost everything you will do:

document
nodeType 9 · the entry point into the DOM
element
nodeType 1 · HTML tags, the branches
text
nodeType 3 · the text inside elements
comment
nodeType 8 · hidden notes JS can still read
The four node types you actually work with, and their nodeType numbers.

Comments earn their spot on that list because JavaScript can read them even though the user never sees them — a place to stash data or markers in the page when you need one.

The demo below walks the child nodes of a small block that mixes an element, some text, and a comment. Each node reports its nodeName and nodeType, and the comment reveals its hidden contents — proof that JavaScript sees what the reader cannot.

interactiveReading nodeType off every child

See it for yourself

You do not have to imagine these trees. Several tools show you the real one.

One quick option is the Live DOM Viewer: type HTML into a box and watch the matching DOM tree appear instantly. Good for building intuition about whitespace and autocorrection.

The tool you will actually live in, though, is the browser’s built-in developer tools. Open any HTML page, launch DevTools, and switch to the Elements tab. It looks roughly like this:

ElementsConsoleSourcesNetwork▾ <html>▸ <head></head>▾ <body>“The truth about foxes.”</body></html>
A schematic of the DevTools Elements tab: tabs across the top, the live DOM tree below, with the selected node highlighted.

Click through the tree, expand nodes, read their details. One thing to note: the view here is simplified. Text nodes are shown inline as plain text, and the blank whitespace-only nodes are hidden completely. That trims the clutter, and since you mostly care about element nodes, it rarely gets in your way.

The little pointer button in the top-left corner of DevTools lets you pick an element straight off the page with your mouse and jump to it in the Elements tree. On a large page with a sprawling DOM, that is the fastest way to locate one specific element.

The same thing is a right-click away: Inspect in the page’s context menu drops you onto that exact node.

The truth about foxes.BackReloadSave as…View page sourceInspect
Right-clicking an element on the page opens the context menu; its last item, Inspect, jumps straight to that node in the Elements tree.

The panel beside the tree has several subtabs worth knowing:

  • Styles — every CSS rule hitting the current element, listed rule by rule, browser defaults in gray. Almost all of it is editable in place, including the box dimensions, margins, and padding shown in the diagram below the rules.
  • Computed — the final CSS grouped by property, so for each one you can trace which rule won, inheritance and all.
  • Event Listeners — the event handlers attached to DOM elements (a topic for the next part of this course).
  • …and more beyond those.

The fastest way to learn these panels is to poke at them. Most values edit in place, so change things and watch the page respond.

Interaction with console

While you explore the DOM visually, you will often want to run JavaScript against a node — grab it, change it, see what happens. DevTools makes it easy to pass nodes between the Elements tab and the Console.

Start here:

  1. Select the first <li> in the Elements tab.
  2. Press Esc to open the Console in a drawer beneath it.

The element you just selected is now available in the Console as $0. The one selected before it is $1, and so on back through your recent selections.

So you can run code against your current selection directly. $0.style.background = 'red' paints the selected list item red:

>$0.style.background = ‘red’‘red’$0 is the node you selected in Elements — now painted red.
The Console drawer: running $0.style.background = 'red' against the currently selected node and seeing the assignment echoed back.

That is the trip from Elements to Console. There is a return trip too. If you have a variable pointing at a DOM node, call inspect(node) in the Console to reveal it in the Elements pane.

Or just log the node and explore it right there in the Console — here is document.body:

>document.body▸ <body></body>
Logging document.body returns the node itself, printed as an expandable element you can open and explore right in the Console.

DevTools earns its keep here: explore the tree, try a change, and see immediately when something breaks.

Summary

The browser turns your HTML (or XML) into the DOM tree, a model of node objects it keeps in memory.

  • Tags become element nodes and form the branches of the tree.
  • Text becomes text nodes, always leaves.
  • Everything else in the HTML gets a node too, comments and the doctype included.

You can inspect and edit that tree by hand with DevTools, which is the single most useful habit while learning. Chrome’s tools have deep official documentation; skim the menus first to get the shape of things, then read the docs to fill gaps.

DOM nodes carry properties and methods for moving between them, reading them, and reshaping the page. That is where the next chapters go.