skip to content

What is the render tree that a browser builds before layout, how does it relate to the DOM tree, and which parts of the document never appear in it?

level: middleimportance: should knowfreq 58%

answer

  1. DOM plus computed style, not DOM alone
  2. what is in head never renders
  3. boxes with no node behind them
  4. one element can be many fragments
  5. shadow DOM is flattened first

basics

~20 s

The render tree pairs each rendered node with its computed style to produce the boxes the browser lays out. It is not a copy of the DOM: head content and display:none subtrees are missing, pseudo-elements add boxes with no node, and one element can produce several boxes.

solid answer

~50 s

After the DOM is built and every element has a computed style, the browser combines the two into a tree of boxes — the render tree, which is what layout, paint and hit testing actually walk. It deliberately diverges from the DOM. Nodes that are never rendered are absent: everything in `<head>`, comments, and any `display: none` subtree. Nodes that are invisible but still laid out are present: a `visibility: hidden` element has a box. Boxes appear that have no node behind them, notably `::before` and `::after` with a `content` value, and list markers. The mapping is not one-to-one either — an inline element wrapped across lines produces one fragment per line, and `display: contents` makes an element generate no box while its children still do. With shadow DOM, the browser builds boxes from the flat tree, so slotted nodes are laid out where the slot is, not where they were authored.

code

html · 11 lines
html
<style>
  .grid { display: grid; grid-template-columns: 1fr 1fr; }
  .wrapper { display: contents; }
  .card::before { content: "→ "; }
</style>
<div class="grid">
  <div class="wrapper">
    <div class="card">one</div>
    <div class="card">two</div>
  </div>
</div>

go deeper

for a junior

Know that the browser builds a separate tree of boxes from the DOM plus the computed styles, and that things like head elements and display: none subtrees are simply not in it. That framing alone answers the question.

for a middle

Explain the divergences concretely: nodes with no box, boxes with no node such as pseudo-elements and markers, anonymous boxes, and an inline element fragmenting across lines. Be ready to place visibility: hidden correctly — it is present.

for a senior

Connect the structure to observed behaviour: zero measurements on hidden elements, the cost of toggling display, and shadow DOM being laid out from the flat tree rather than the authored tree. Debugging slot and containment bugs starts here.

for a principal

Judge when leaning on this machinery is worth it. Wrappers erased with display: contents, heavy use of pseudo-element boxes and deep shadow trees all shift complexity into the box tree, where it is harder to reason about and to test than plain markup.

## What the tree is The DOM is a tree of *nodes* — a faithful model of the document's structure. Layout does not operate on nodes; it operates on **boxes**. So once the DOM exists and style resolution has produced a computed style for every element, the browser builds a second structure, commonly called the render tree (engines name it the layout tree or the box tree), whose entries are the boxes to be sized, positioned, painted and hit-tested. Interviewers ask about it because most people assume it mirrors the DOM. It does not, in four distinct ways. ## 1. Nodes that produce no box - Everything inside `<head>` — `<title>`, `<meta>`, `<link>`, `<script>`, `<style>`. These are in the DOM and reachable from script, but they render nothing. - Comment nodes, and `<script>`/`<template>` content in the body. A `<template>`'s contents live in a separate inert document fragment and are never rendered until cloned into the document. - Any element with `display: none`, together with its entire subtree. - Whitespace-only text between block-level elements, which usually produces no text box. Note what is *not* on that list: `visibility: hidden` elements are in the render tree. They are laid out and take up space; only paint skips them. ## 2. Boxes that have no node The reverse direction also breaks. `::before` and `::after` generate boxes when a `content` value is set, and those boxes have no DOM node — which is exactly why `document.querySelector` cannot select them and why you read their styles with the second argument of `getComputedStyle`. List items generate a marker box. Anonymous boxes appear where the box tree needs structure the markup does not provide: put raw text next to a block child inside a block container and the text is wrapped in an anonymous block box so the container holds only blocks. ## 3. One element, many boxes A node maps to a box only in the simple case. An inline element whose text wraps over three lines contributes one fragment per line. An element inside a multi-column container fragments across columns. So "the number of boxes" is a property of layout, not of the markup. ## 4. display: contents `display: contents` is the mirror image of `display: none`: the element itself generates no box, but its children generate theirs and are laid out as if they were direct children of the element's parent. It is the tool for letting a wrapper element vanish from a flex or grid layout while remaining in the DOM. ```css .wrapper { display: contents; } /* children become grid items of the grandparent */ ``` Be careful with it: removing a box can also remove semantics that browsers derived from that box, so it is not a free abstraction on elements with meaning of their own. ## Shadow DOM and the flat tree When shadow roots are involved, the browser does not lay out the DOM tree as authored. It computes the **flat tree**: for each shadow host, the shadow root's children replace the host's children, and light-DOM nodes assigned to a `<slot>` appear at the slot's position. Boxes are generated from that flattened structure, which is why slotted content inherits from and is laid out inside the shadow markup even though `parentNode` still reports the original light-DOM parent. ## Why the distinction pays off Once you see the render tree as a separate structure, several everyday behaviours stop being surprising: a `display: none` element measures as zero because it has no box; toggling `display` is more expensive than toggling `visibility` because boxes must be constructed; and a style change that only affects a paint-time property does not need the box tree rebuilt at all. It also frames the next stage cleanly — layout is simply the traversal that gives each of these boxes a size and a position.

  • How would you read the styles of a `::before` pseudo-element from script, given it has no DOM node?
    Pass the pseudo-element string as the second argument to `getComputedStyle`, for example `getComputedStyle(el, '::before').content`. There is no node to select, so selector APIs cannot reach it, and it cannot be a listener target either — events from it are dispatched against the originating element.
  • What is an anonymous box, and when does the browser create one?
    It is a box the layout engine invents to keep the box tree well-formed, with no element behind it. The classic case is a block container holding both raw text and a block-level child: the stray text is wrapped in an anonymous block box so the container's children are uniformly blocks. Anonymous boxes inherit from their parent and cannot be targeted by selectors.
  • Does an element in a `<template>` element's content appear in the render tree?
    No. A template's contents are parsed into a separate inert document fragment rather than into the document, so no styles apply, no boxes are generated, and no resources inside it are fetched. It only becomes renderable once you clone the fragment and insert it into the document.

saying these in an interview costs you the question

  • Says the render tree is a one-to-one copy of the DOM
  • Claims visibility: hidden elements are absent from it
  • Thinks pseudo-elements are DOM nodes
  • Confuses display: contents with display: none
  • Assumes slotted nodes are laid out where they were authored

context