The render tree and layout
The DOM and the computed styles from the previous lesson combine into a second, smaller tree holding only what will actually be drawn — and layout then walks that tree to compute the exact size and position of every box, which is the moment content and style finally become geometry.
The render tree and layout
You've now got two trees sitting in memory — the DOM, built from the HTML bytes, and the CSSOM, built from the CSS bytes, with every element's final computed style resolved out of the cascade. Neither one, on its own, is enough to draw anything. The DOM knows there's a <div>; it has no idea how big that <div> is. The CSSOM knows width: 50% was the winning declaration; it has no idea what 50% resolves to in pixels, because that depends on the parent's size, which depends on its parent, all the way up to the viewport. Something has to combine the two trees and then turn "these are the rules" into "here is a box, and here is exactly where it sits."
That's two separate jobs, and this lesson is about both of them. First, the browser walks the DOM and the resolved styles together and builds the render tree — a pruned, style-attached copy of the visible page. Then it walks that tree and runs layout, computing real numbers: width, height, x, y, for every box on the page. Only after layout finishes does anything have a location it can be painted at.
Building the render tree: only what gets drawn
The render tree — sometimes called the layout tree — is built by walking the DOM in order and, for each node, deciding whether it produces a visible box at all. If it does, that node is copied into the render tree paired with its fully resolved computed style from the previous lesson. If it doesn't, the node is simply skipped — it stays in the DOM, JavaScript can still find it with document.querySelector, but it has no entry in the tree that layout and paint will ever look at.
A few categories never make it in, by definition rather than by any style rule:
Non-rendered elements. <head>, <meta>, <title>, <script>, <style> — these carry no visual representation at all. They're metadata and instructions for other parts of the pipeline, not content, so there's nothing for the render tree to hold.
display: none. This is the one that trips people up, because it's a style declaration, applied to an ordinary visible element like a <div> or an <img>, and yet its effect is to remove that element from the render tree entirely — not to draw it invisibly, to not draw it at all. No box, no geometry, no space reserved for it in the layout of its siblings.
What's included, but not the way you'd expect
Two categories land in the render tree in a way that surprises people who assume "in the render tree" means "looks like the DOM, just filtered."
visibility: hidden is in the tree. Unlike display: none, a visibility: hidden element still gets a full render-tree entry and a full layout pass — it has real geometry, it occupies real space, its siblings lay out around it exactly as if it were visible. The only thing that changes is the paint step skips drawing its pixels. This is the mechanical difference behind advice you've probably followed without deriving it: toggling display reflows the page, because you're adding or removing a box from the tree; toggling visibility never reflows anything, because the box was there, sized and positioned, the whole time.
/* Removed from the render tree entirely — no box, no space reserved.
Siblings shift to fill the gap. Toggling this triggers layout. */
.tooltip-hidden {
display: none;
}
/* Still in the render tree, still occupies its box, still laid out.
Only paint skips it. Toggling this never triggers layout. */
.tooltip-invisible {
visibility: hidden;
}Generated content gets its own box. ::before and ::after don't exist anywhere in the DOM — there's no node for them to correspond to — but if a matched rule gives them a content value, the render tree grows a box for them anyway, as if a real child element had been inserted at that position.
.required::after {
content: " *";
color: var(--terracotta);
}That single line produces a genuine render-tree box: it gets computed style, it gets laid out inline after the element's real content, and it gets painted. Inspect the DOM in devtools and the label element has no extra child; inspect the render tree (the accessibility tree and the "Computed" pane both hint at this) and there's a box that isn't backed by any node you wrote.
Layout: turning boxes into numbers
The render tree tells the browser what exists and what style it has. It says nothing yet about size or position. Layout — also called reflow, because it's the same computation whether it's running for the first time or re-running after a change — is the pass that walks the render tree and, for every box, works out exactly how big it is and exactly where it sits, in real pixels, relative to its containing block (usually its parent), and ultimately relative to the viewport.
Three things layout has to account for, in order of how directly they show up in everyday CSS:
The box model. Every rendered element is a rectangle built from four nested regions: content, then padding, then border, then margin, working outward. width and height set the content box's size by default; padding and border add to it (unless box-sizing: border-box folds them back in); margin is space claimed outside the border that pushes neighboring boxes away but isn't part of the box itself — which is why two adjacent vertical margins can collapse into one, a box-model quirk that has confused nearly everyone at least once.
Normal flow. With no positioning or flex/grid involved, boxes lay out according to their display type: block-level boxes (div, p, section) stack vertically, each taking the full width available and starting below the previous one; inline-level boxes (span, a, text runs) flow horizontally within a line, wrapping to a new line only when they run out of horizontal room. This is the default algorithm every other layout mode is defined as a departure from.
Positioning schemes and layout modes. position: static is normal flow, full stop. position: relative still occupies its normal-flow slot but can be visually offset from it with top/left/etc. position: absolute is removed from normal flow entirely and positioned against its nearest positioned ancestor instead of its natural parent — which is why an absolutely positioned element doesn't reserve space where it "would have" sat. position: fixed does the same but against the viewport, ignoring scrolling. And display: flex or display: grid replace the block/inline flow algorithm for an element's children with their own sizing and placement rules — flex distributing space along a main axis, grid placing items into an explicit two-dimensional track structure. All of these are, mechanically, just different rules for the same question layout is always answering: given this box's containing block, what's this box's size and position?
Layout is a tree traversal: it walks the render tree, and for each box it needs the box's own style plus its containing block's size to produce a concrete answer — width, height, x, y. The output is effectively the render tree with real numbers attached to every node, and that geometry is exactly what the next stage needs to have anything to draw.
Layout is expensive, and it ripples
That containing-block dependency is also why layout isn't cheap. A box's size can depend on its children (height: auto sizing to content), and a box's position can depend on every box before it in the flow (block boxes stack, so inserting or resizing one shifts everything after it). Change one box's geometry and, in the worst case, the effect propagates outward through its ancestors and forward through its siblings — the browser can't always contain the damage to just the box you touched. This is exactly the cost side of the story the layout, paint, composite lesson walks through property by property — which CSS changes force layout to re-run and which don't — so that lesson is where to go for the full "why transform is cheap and width isn't" argument rather than repeating it here. And if your own code is the one forcing that recomputation to happen synchronously, mid-script, instead of on the browser's normal schedule, that's the specific trap the layout thrashing lesson covers.
Where this goes next
Layout's output — a tree of boxes, each with a real size and a real position — is exactly the input the next stage needs. Nothing can be painted until it's known where to paint it, which is why paint always runs after layout, never before. Paint, layers, and compositing picks up right here: turning this geometry into actual pixels, deciding what gets its own compositor layer, and assembling the final frame.
Go deeper
- MDN — Determining the dimensions of elements — A concrete walkthrough of the box model regions this lesson names — content, padding, border, margin — and how each is measured.
- MDN — Controlling visibility — The authoritative contrast between display:none and visibility:hidden, including the render-tree consequences this lesson leans on.
- MDN — CSS positioned layout — How static, relative, absolute, and fixed positioning each change a box's containing block and its place in normal flow.
Check yourself
Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.
- What two inputs does the browser need to build the render tree, and what does each node in that tree end up holding?
- Name three things that exist in the DOM but never appear in the render tree, and explain why each is excluded.
- Why is visibility:hidden in the render tree while display:none is not, and what's the practical consequence for layout when you toggle each?
- How can a ::before pseudo-element end up with a render-tree box when there's no corresponding DOM node for it?
- What is a containing block, and why does a box's size and position depend on it rather than being computable in isolation?
- Contrast normal flow for block-level and inline-level boxes, then explain how position:absolute changes both the containing block and whether space is reserved in normal flow.
- Why can changing one box's geometry force a large part of the page to be re-laid-out, and which lesson should you read next for exactly which property changes trigger that?