You write some HTML, sprinkle in a bit of CSS, maybe a touch of JavaScript, and a moment later a fully styled page appears on your screen. It feels instant and a little magical. But behind that single moment, the browser runs through a precise sequence of steps, turning your plain text files into colored pixels arranged on a grid.
That sequence has a name: the rendering pipeline. Understanding it is one of those things that quietly makes you a better developer. Once you can picture how the browser works, a lot of mysterious behavior suddenly makes sense — why a page flickers, why a certain CSS change makes scrolling feel sluggish, why moving an element with one property is silky-smooth while another property stutters. This article walks through the whole pipeline slowly, in plain language, so you come away with a clear mental model.
Starting point: just text
When the browser downloads a web page, what it actually receives is text. Your HTML is a long string of characters. Your CSS is another string. The browser cannot draw a string of characters directly — it has no idea where a heading goes or what color a button should be until it has understood that text and built something structured out of it.
So the first job is parsing: reading the raw text and converting it into organized data structures the browser can work with. There are two main structures it builds, and they are the foundation of everything else.
Building the DOM
The browser reads your HTML from top to bottom and turns it into a tree of objects called the DOM — the Document Object Model. “Tree” is the key word here. Just like a family tree, every element has a parent and may have children. An <article> might contain a <h2> and a few <p> elements; those paragraphs are children of the article, the article is a child of <body>, and so on.
Document
└── html
└── body
└── article
├── h2 ("The Rendering Pipeline")
└── p ("You write some HTML...")
Each box in that tree is a node — a live object the browser keeps in memory. This is also exactly what JavaScript talks to when you write something like document.querySelector('h2'). The DOM is not your HTML file; it is the browser’s structured, in-memory representation of it.
The DOM is a model, not your file
People often say “the DOM” when they mean “the HTML.” They are related but not the same. Your HTML is text on disk. The DOM is the tree of objects the browser builds from that text, and which it keeps updating as JavaScript runs. Change the DOM and the page changes; the original HTML file stays untouched.
Building the CSSOM
While it is figuring out structure from your HTML, the browser also needs to figure out style. It reads every stylesheet — your external CSS files, <style> blocks, and inline styles — and builds a second tree called the CSSOM, the CSS Object Model.
The CSSOM mirrors the structure of the DOM but carries styling information: which font, which color, how much padding, and so on. It also handles inheritance and cascading. If you set a color on <body>, the CSSOM works out that every paragraph inside it should inherit that color unless something more specific overrides it. By the time the CSSOM is done, the browser knows the final, computed style for every element.
| Structure | Built from | Describes |
|---|---|---|
| DOM | HTML | The content and its structure |
| CSSOM | CSS | The styles that apply to that content |
Combining them into the render tree
Now the browser has two trees: one for content (DOM) and one for style (CSSOM). On their own, neither is enough to draw anything. Content without style has no appearance; style with no content has nothing to paint. So the browser combines them into a third tree: the render tree.
The render tree contains only the things that will actually be visible, each one paired with its computed styles. This distinction matters. Some nodes from the DOM never make it into the render tree. The <head> element holds metadata, not visible content, so it is left out. Anything styled with display: none is dropped too — it exists in the DOM, JavaScript can still see it, but it produces nothing on screen, so the render tree skips it.
display: none vs visibility: hidden
These two look similar but behave differently in the pipeline. display: none removes the element from the render tree entirely — it takes up no space at all. visibility: hidden keeps the element in the render tree and still reserves its space; it is simply painted as invisible. That small difference changes how the next steps treat the element.
Layout: working out the geometry
The render tree knows what to show and how it should look, but not yet where everything goes or how big it is. Figuring that out is the layout step (older browsers and many articles call it reflow — same idea, different name).
During layout, the browser calculates the exact geometry of every visible element: its position on the page and its width and height in pixels. This is where percentages, em units, flex, grid, and “fill the available width” all get resolved into concrete numbers. A width: 50% only becomes meaningful once the browser knows how wide the parent actually is, and layout is where that math happens.
Layout is inherently relational. Where one element sits depends on the elements around it, and the size of a container depends on its children. So a single change can ripple outward: make one paragraph taller, and everything below it has to shift down. That ripple is exactly why layout can be expensive, which brings us to a key idea you should hold onto.
Reflow vs repaint
Two words come up constantly when people talk about rendering performance: reflow and repaint. They describe what the browser has to redo when something on the page changes, and they cost very different amounts of work.
A reflow (a layout pass) happens when you change something that affects geometry — size or position. Changing an element’s width, adding text that makes a box taller, inserting a new element, toggling display — all of these force the browser to recompute positions and sizes, often for many elements at once because of that ripple effect. Reflow is the more expensive of the two.
A repaint happens when you change something purely visual that does not affect geometry — for example, changing a text color, a background color, or visibility. The element stays exactly where it was and keeps the same size, so the browser can skip layout and just refill the affected pixels. Cheaper than a reflow, though still not free.
| Change you make | What the browser redoes | Relative cost |
|---|---|---|
| Change width, height, or position | Reflow (layout) + repaint | Higher |
| Add or remove a visible element | Reflow (layout) + repaint | Higher |
| Change text or background color | Repaint only | Lower |
Change visibility to hidden |
Repaint only | Lower |
The practical takeaway: a reflow almost always forces a repaint afterward (if geometry moved, the pixels must be redrawn), but a repaint does not require a reflow. Touching geometry is the more costly choice, so when you have the option, prefer changes that only repaint.
Paint: filling in the pixels
Once layout has decided where every box lives and how big it is, the browser moves on to paint. This is the step where it actually fills in pixels — drawing text glyphs, background colors, borders, shadows, images, and every other visual detail onto a surface.
Think of layout as the architect drawing the blueprint with exact measurements, and paint as the crew applying color to every surface according to that blueprint. Paint does not decide where things go — layout already did that. It only decides what color each pixel should be.
For a complex page, painting can be broken into several layers drawn separately, which sets up the final step.
Compositing: assembling the layers
Modern browsers often do not paint the whole page onto one flat surface. Instead they split parts of the page into separate layers, paint each layer independently, and then stack them together to produce the final image you see. That stacking-and-combining step is called compositing.
A useful analogy is the old-fashioned animation cel: artists would draw a static background on one sheet and a moving character on a transparent sheet laid on top. To animate the character, you only redraw the top sheet and slide it — the background never changes. Browser layers work much the same way.
This is the secret behind buttery-smooth animations. If an element lives on its own layer, the browser can move that layer around — shifting it, scaling it, fading it — without redoing layout or even repainting the layer’s contents. It just re-composites the existing layers in new positions. That is dramatically cheaper than reflowing and repainting on every frame, and it is why animating certain properties feels smooth while animating others stutters.
Some properties stay on the cheap path, some don't
As a rule of thumb, animating an element’s position with transform or its opacity with opacity can often be handled by compositing alone — the cheap path. Animating geometry properties like width, height, top, or left forces a reflow on every frame — the expensive path. Same visual goal, very different cost. This is conceptual here; the deeper “how to keep animations smooth” details belong to web performance, which is a topic on its own.
The whole pipeline at a glance
Putting every step in order, here is the journey from text to pixels:
HTML ─► parse ─► DOM ─┐
├─► Render Tree ─► Layout ─► Paint ─► Composite ─► Pixels
CSS ─► parse ─► CSSOM ┘ (geometry) (fill) (stack layers)
Read it left to right: two streams of text get parsed into the DOM and CSSOM, those merge into the render tree, layout works out the geometry, paint fills the pixels, compositing stacks the layers, and the finished frame lands on your screen. Every visible change you make on a page re-enters this pipeline at some point — and how far back it re-enters is exactly what determines whether the change is cheap or expensive.
Recap
The rendering pipeline is the browser’s recipe for turning code into a picture, and now you can picture each stage:
- Parsing reads your raw HTML and CSS text.
- The DOM is the tree of content objects built from HTML; the CSSOM is the tree of style information built from CSS.
- The render tree combines both and keeps only what is actually visible.
- Layout (reflow) computes the geometry — where every element sits and how big it is.
- Paint fills in the actual pixels: colors, text, borders, shadows.
- Compositing stacks separately painted layers into the final image.
- A reflow redoes geometry and is costly; a repaint only refills pixels and is cheaper; compositing changes are cheapest of all.
Hold this model in your head and the browser stops being a black box. You will start to recognize, almost by instinct, which changes are cheap and which are expensive. From here, the natural next step is web performance — where you take this understanding of reflow, repaint, and compositing and turn it into concrete techniques for building pages that stay fast and smooth even when a lot is happening on screen.