Module V · Viewing

Style, layout, paint, composite

The browser now holds two trees: the DOM from Module 3 and the CSSOM built from the render-blocking CSS of Module 4. Neither is pixels. This module is the pipeline that turns structure and style into a lit screen — four stages, each with a distinct job, each with a distinct cost. Understanding the stages is what separates “my animation is janky” from knowing exactly which stage is running sixty times a second and why.

5.1 Two trees, one picture

The journey from the two trees to the screen is a fixed sequence: compute the styles, compute the geometry, draw the pixels, assemble the layers. The names are style, layout, paint, and composite. The order is not negotiable, because each stage consumes the output of the previous one — you cannot place a box before you know its font size, and you cannot paint it before you know where it goes.

The rendering pipelineThe DOM and CSSOM feed the style stage, which produces the render tree, then layout computes geometry, then paint produces draw records, then composite assembles layers on the GPU and sends them to the screen. Style, layout, and paint run on the main thread; compositing runs on the compositor thread and GPU.DOMCSSOMStyleLayoutPaintCompositerender treegeometrydraw recordslayers → screenmain threadcompositor + GPU
Figure 5.1 — The pipeline. Style, layout, and paint run on the main thread — the same thread that runs your JavaScript (Module 6). Compositing runs on a separate thread and the GPU, which is the key to why some changes are nearly free.

5.2 Style: resolving the cascade

The style stage walks the DOM and, for every element, computes its computed style — the final value of every CSS property after the cascade, specificity, inheritance, and custom-property resolution have all been applied. Selector matching happens here: the engine decides which of the CSSOM's rules apply to which nodes. The output is the render tree (WebKit and Blink call it the layout tree; Gecko, the frame tree) — a tree of the elements that will actually be drawn. It is not the DOM. Elements with display: none are absent entirely; pseudo-elements like ::before are present though they exist in no markup; visibility: hidden elements are present but will paint nothing. The render tree is the DOM filtered and annotated for display.

5.3 Layout: computing geometry

Layout — historically called reflow in Gecko — takes the render tree and computes the exact position and size of every box: the box model, normal flow, flex, grid, the recent fragmentation work. Its output is a geometry tree where every node knows its rectangle in the page. Layout is the expensive stage, and it is expensive because it is global-ish: changing the width of one element can change the position of its siblings, its children, and everything after it in flow. One small write can force the browser to recompute the geometry of a large part of the page.

The classic performance trap lives here: forced synchronous layout, also called layout thrashing. The browser batches your style and layout changes to run them once per frame — but if your JavaScript writes a style and then reads a geometry property like offsetWidth, the browser must flush layout immediately to answer the read, because the answer depends on the write. Do that in a loop — write, read, write, read — and you force dozens of synchronous layouts in a single frame, turning a smooth interaction into a stall. The fix is to batch: read all the geometry first, then write all the changes. This is a Module 6 concern as much as a Module 5 one, because it is about when on the main thread the work happens.

5.4 Paint and composite

Paint turns the laid-out boxes into draw commands: fill this rectangle, stroke this border, render this text run, draw this shadow. The browser does not usually paint straight to the screen; it records lists of paint operations, often grouped into separate layers for parts of the page that can be handled independently. Rasterization — actually producing the pixels from those commands — frequently happens on the GPU.

Composite is the final assembly. The page is divided into layers; each is rasterized; the compositor places them in the right order with their transforms and opacity and hands the result to the screen. The decisive fact is that the compositor runs on its own thread and the GPU, separate from the main thread. Once a layer has been painted, the compositor can move it, scale it, rotate it, or fade it without repainting and without re-running layout — it is just re-placing an existing texture. That is the entire reason the next diagram matters.

5.5 What a change costs

Every visual change re-enters the pipeline, but not always at the top. The stage where a change enters determines its cost: a change that forces layout is expensive because paint and composite must follow; a change that only composites is nearly free because it skips the main thread's heavy stages entirely.

The cost of a property change by pipeline depthChanging width or top runs layout, paint, and composite — the heaviest. Changing color or box-shadow skips layout but runs paint and composite. Changing transform or opacity skips both layout and paint and runs only composite on the GPU — the lightest.LayoutPaintCompositewidth, top, marginrunsrunsrunsheaviestcolor, box-shadowskippedrunsrunsmediumtransform, opacityskippedskippedGPUlightestthe further down the pipeline a change enters, the cheaper it is — and the more it can leave the main thread
Figure 5.2 — Why “animate transform and opacity” is the most repeated advice in front-end performance. Those two properties enter the pipeline at the bottom, run only on the compositor and GPU, and so can hit sixty frames a second while the main thread is busy. Animating width or top forces the whole machine to run every frame.
Cheap pixels come from the bottom of the pipeline. If you can express a change as a transform or an opacity, the GPU will do it while the main thread sleeps.

Promoting an element to its own compositor layer — with will-change or a 3D transform — is how you tell the browser “this will move; keep it ready.” It is powerful and easy to overuse: every layer costs memory, and a page that promotes everything trades smooth animation for exhausted GPU memory. Layer promotion is a hint to spend, not a free win.

5.6 Layout shift: when geometry changes late

Module 4 showed resources arriving over time. Some of them change geometry when they land, and that re-runs layout — visibly. An image with no declared dimensions takes up no space until it loads, then suddenly claims its size and shoves everything below it downward. A web font that arrives after first paint may have different metrics than the fallback, so the text re-flows the instant it swaps. The user, mid-read or mid-tap, watches the page jump. This is layout shift, measured as Cumulative Layout Shift, and it is the pipeline's most user-hostile behavior.

Layout shift when a late resource claims spaceBefore an image loads, text sits high on the page. After the image loads it claims its full height and pushes the text downward, causing a visible jump. Reserving the space ahead of time prevents the shift.while loading (no reserved size)image (0 height yet)text sits highafter load (image claims height)imagetext jumps down
Figure 5.3 — The shift. The cure is to reserve the space before the resource arrives: width and height (or aspect-ratio) on images so the box is sized from the start, and font-display plus matched fallback metrics so a font swap does not re-flow. Demo Company's building image and web-font pages are this diagram, live.

5.7 The lab: watch the pipeline run

DevTools exposes every stage. In the Rendering tab, turn on “Paint flashing” to see green flashes wherever the browser repaints, and “Layer borders” to see which elements have been promoted to their own compositor layer. In the Performance panel, record an interaction and read the main-thread track: purple is layout, green is paint, and you can spot a forced synchronous layout as a tall layout spike wedged inside your scripting time. Load Demo Company's web-font page and watch the text repaint — or shift — the moment the font swaps in.

5.8 What you now have

The four-stage pipeline from two trees to a screen: style resolves the cascade into a render tree; layout computes geometry and is the expensive, global stage where forced synchronous layout lurks; paint records draw commands into layers; composite assembles those layers on a separate thread and the GPU. The actionable core is the cost model — a change that triggers layout drags the whole pipeline behind it, while a transform or opacity change rides the compositor nearly for free — and its corollary, layout shift, where late-arriving resources re-run layout in the user's face.

Every heavy stage in this module — style, layout, paint — runs on the main thread, and that thread does one thing at a time. It also runs your JavaScript. Module 6 is the scheduler that decides how rendering and scripting share that single thread: the event loop, the sixteen-millisecond frame budget, the task and microtask queues, and requestAnimationFrame — the OS scheduler from Module 1, finally in full.

The pipeline is fixed; the cost is yours to choose. You pick which stage your changes re-enter, and the user feels the difference at sixty frames a second.