The short answer
Quick answer: A browser turns code into pixels in a sequence of steps called the critical rendering path. It parses HTML into a tree of elements, the DOM. It parses CSS into a tree of style rules, the CSSOM. It combines them into a render tree of the elements that are actually visible, with their computed styles. Layout then calculates the exact size and position of every box. Paint fills in the pixels: text, colours, images, borders. Finally, compositing assembles separately painted layers into the image you see. When something changes, the browser repeats only the steps it has to, and the more it must repeat, the slower the update.
This article picks up where what happens when you type a URL and press Enter leaves off: the HTML has arrived.
Step 1: Parse HTML into the DOM
The browser reads the HTML as a stream of bytes, converts them to characters, groups those into tokens (<p>, text, </p>), and builds a tree of nodes: the Document Object Model.
<html>
<body>
<h1>Hello</h1>
<p>Some <em>text</em>.</p>
</body>
</html>
becomes a tree with html at the root, body beneath it, then h1 and p, with em inside the p.
Two properties matter:
- It is incremental. The browser starts building and showing content before the whole file has downloaded.
- It is forgiving. Unclosed tags and other mistakes are repaired according to rules in the HTML standard.
While the main parser works, a preload scanner looks ahead in the raw HTML for stylesheets, scripts, fonts and images, and starts downloading them early.
Step 2: Parse CSS into the CSSOM
CSS is parsed into the CSS Object Model, a tree of rules. The browser then works out which rules apply to each element and resolves conflicts using the cascade: origin, specificity and order.
CSS is render-blocking. The browser will not paint until it has processed the CSS in the page's <head>, because painting first and restyling a moment later would produce a jarring flash of unstyled content. So large or slow stylesheets directly delay the first paint.
Step 3: JavaScript gets in the way
A plain <script> tag stops the HTML parser. The browser must download the script (if it is external), then execute it, before continuing. The reason is that a script can change the document, for example with document.write, so the parser cannot safely carry on.
It is worse than it looks: a script may ask about styles, so it must also wait for any CSS above it to finish loading.
| Script tag | Download | Execution | Blocks parsing? |
|---|---|---|---|
<script src> | Immediately | Immediately when downloaded | Yes |
<script async src> | In parallel | As soon as downloaded, in any order | Only while executing |
<script defer src> | In parallel | After the HTML is parsed, in order | No |
<script type=module> | In parallel | Deferred by default | No |
Use defer for scripts that need the DOM, and async for independent ones such as analytics.
Step 4: The render tree
The browser combines DOM and CSSOM into a render tree containing only what will be drawn:
- Elements with
display: noneare left out, along with everything inside them. - Non-visual elements such as
<head>and<script>are left out. - Elements with
visibility: hiddenstay in: they are invisible but still occupy space. - Pseudo-elements such as
::beforeare added, even though they are not in the DOM.
Step 5: Layout
Now the browser calculates geometry: the exact width, height and position of every box, based on the viewport size, the box model, fonts and the content. This step is also called reflow.
Layout is costly because boxes depend on each other. Changing one element's width can move its siblings, resize its parent and reflow its children's text.
Step 6: Paint
With positions known, the browser records the drawing operations for each element, in the right order: backgrounds, borders, text, shadows, images. The step that converts those instructions into actual pixels is rasterisation.
Step 7: Compositing
The page is not painted as one flat picture. Some parts are placed on their own layers, for example elements with a 3D transform, fixed-position elements, videos, or elements marked with will-change.
The compositor combines the layers into the final frame, usually on the GPU. The advantage is that some changes need no layout or paint at all:
- Changing
transformoropacityon a layer only requires re-compositing. - That work happens on a separate thread from JavaScript, so such animations stay smooth even when the main thread is busy.
- Scrolling is mostly a compositor operation for the same reason.
Chrome's Inside look at modern web browser series and the web.dev guide to the critical rendering path explain these stages in depth.
What it costs to change something
After the first render, every change re-runs part of the pipeline. How much depends on what you changed.
| You change | Layout | Paint | Composite | Cost |
|---|---|---|---|---|
width, height, margin, top, font-size, adding or removing elements | Yes | Yes | Yes | Highest |
color, background, box-shadow, visibility | No | Yes | Yes | Medium |
transform, opacity | No | No | Yes | Lowest |
That is why animations should use transform and opacity instead of top, left or width.
The 16 millisecond budget
Most screens refresh 60 times a second, which gives the browser about 16.7 ms to produce each frame. JavaScript, style calculation, layout and paint all run on one main thread. If a script runs for 100 ms, no frames are produced and the page cannot respond to input for that time. The result is visible stutter, known as jank. See what is the event loop.
Layout thrashing
A common mistake forces the browser to do layout repeatedly inside a loop:
// Bad: read, write, read, write. Layout is recalculated every iteration.
for (const box of boxes) {
box.style.width = container.offsetWidth / 2 + "px";
}
// Good: read once, then write.
const half = container.offsetWidth / 2;
for (const box of boxes) {
box.style.width = half + "px";
}
Reading a layout property such as offsetWidth after changing styles forces the browser to recalculate layout immediately so it can give a correct answer. Batch your reads, then your writes.
Frameworks reduce unnecessary DOM changes in different ways; see what the virtual DOM is.
How this connects to performance metrics
Google's Core Web Vitals measure the user's experience of this pipeline:
| Metric | Measures | Affected by |
|---|---|---|
| Largest Contentful Paint (LCP) | When the main content appears | Server speed, render-blocking CSS and scripts, image loading |
| Interaction to Next Paint (INP) | How quickly the page responds to input | Long JavaScript tasks on the main thread |
| Cumulative Layout Shift (CLS) | How much content jumps around | Images without dimensions, late-loading fonts and ads |
Making rendering faster
- Send less CSS, and inline the small amount needed for the first screen.
- Defer JavaScript that is not needed immediately, and ship less of it. See why JavaScript bundles get so big.
- Give images and videos
widthandheight, so the browser reserves space and nothing shifts. - Lazy-load images below the fold, and prioritise the main one.
- Animate with
transformandopacity. - Break up long tasks so the main thread can respond between them.
- Keep the DOM reasonably small.
- Use
content-visibility: autoto skip rendering off-screen sections. - Send HTML that already contains the content where possible; see server-side vs client-side rendering.
Browser developer tools show all of this: the Performance panel records exactly how long parsing, scripting, layout and paint took for each frame.
Frequently asked questions
What is the critical rendering path?
The sequence of steps a browser performs to convert HTML, CSS and JavaScript into pixels: building the DOM and CSSOM, creating the render tree, layout, paint and compositing.
What is the difference between reflow and repaint?
Reflow (layout) recalculates the size and position of elements. Repaint redraws their appearance without changing geometry. Reflow is more expensive and always leads to a repaint.
Why is CSS render-blocking?
The browser waits for CSS before painting so that it does not show unstyled content and then abruptly restyle it.
What is the difference between the DOM and the render tree?
The DOM contains every element in the document. The render tree contains only the visible ones, together with their computed styles.
Conclusion
A browser builds two trees, merges them, measures every box, paints, and composites, and it repeats as little of that as possible when things change. Knowing which changes trigger which steps is the foundation of front-end performance: keep CSS and scripts from blocking the first paint, avoid needless layout, and let the compositor handle animation.
Related articles
- What Happens When You Type a URL and Press Enter
- What Is the Virtual DOM and Why Did React Use It?
- Server-Side vs Client-Side Rendering: Trade-offs Explained
- Why JavaScript Bundles Get So Big (and How to Shrink Them)
