How Browsers Render CSS: A Step-by-Step Explanation
Every time you load a web page, your browser runs a complex pipeline to turn HTML and CSS into pixels on the screen. Understanding this pipeline helps you write more efficient, predictable styles and debug layout issues with confidence.
1. From Network to Text: Fetching HTML and CSS
The rendering process begins as soon as the browser starts receiving bytes from the network:
- HTTP request: The browser requests an HTML document from a URL.
- Response stream: The server responds with bytes that the browser decodes into text (usually UTF-8).
- Incremental parsing: The browser does not wait for the entire document to download; it begins parsing HTML as it arrives.
While parsing HTML, the browser discovers external resources such as stylesheets and scripts via tags like <link rel="stylesheet"> and <style> blocks. It schedules separate network requests for those resources.
2. Building the DOM Tree from HTML
As the browser reads the HTML, it builds the DOM tree (Document Object Model):
- The HTML source is tokenized into tags, text nodes, comments, etc.
- These tokens are turned into nodes (elements, text nodes, etc.).
- Nodes are organized into a tree that mirrors the nesting structure of the HTML document.
Each element becomes a DOM node with properties such as tag name, attributes, parent, children, and event hooks. At this stage, the browser knows the structure of the document but has not yet decided what anything looks like.
3. Fetching and Parsing CSS
While the DOM is being constructed, the browser encounters CSS in three main forms:
- External stylesheets: via
<link rel="stylesheet" href="..."> - Embedded styles: within
<style>...</style>tags - Inline styles: via the
style="..."attribute on elements
For external and embedded CSS, the browser parses CSS into a structured representation:
- The CSS text is tokenized (selectors, properties, values, units, functions).
- Tokens are turned into CSS rules (selector + declaration block).
- Rules are stored in a stylesheet object model, often called the CSSOM (CSS Object Model).
Parsing also involves:
- Handling syntax errors gracefully (unknown properties, invalid values).
- Normalizing values (e.g., converting color names to a canonical representation).
- Understanding at-rules such as
@media,@supports, and@keyframes.
4. Constructing the CSSOM (CSS Object Model)
Once CSS is parsed, the browser has a full CSS Object Model parallel to the DOM:
- Each stylesheet is represented as an object (or set of objects).
- Each rule contains a selector list and a declaration block.
- Declarations map property names to parsed values.
The CSSOM is essential because it lets the browser efficiently:
- Find which rules might apply to each element.
- Recompute styles quickly when something changes (like a class name or viewport size).
- Implement JavaScript access to styles via APIs like
document.styleSheetsandgetComputedStyle.
5. Matching Selectors to DOM Elements
With both the DOM and CSSOM available, the browser can match styles to elements:
- The browser effectively asks, for each element: “Which CSS rules apply to you?”
- This involves selector matching, typically optimized to run from right to left (from the innermost part of the selector back up the ancestor chain).
- Pseudo-classes (e.g.,
:hover,:focus,:nth-child()) and pseudo-elements (e.g.,::before,::after) are considered during this process.
The result of this phase is a list of candidate rules for each element. Conflicts between these rules are resolved in the cascade step.
6. The Cascade: Resolving Conflicting Rules
The cascade is the algorithm that determines which rule “wins” when multiple rules set the same property on the same element. It takes into account:
- Origin and importance:
- Browser (user agent) stylesheet
- User styles (if configured in the browser)
- Author styles (from the page itself)
!importantdeclarations in each origin
- Specificity: More specific selectors override less specific ones.
- Inline styles (via the
styleattribute) have very high specificity. - ID selectors are more specific than class selectors, which are more specific than type selectors.
- Inline styles (via the
- Source order: If specificity and importance are equal, the rule that appears last in the source wins.
After applying the cascade, the browser has a clear winner for each property on each element.
7. Inheritance: Passing Values Down the Tree
Some properties are inherited from parent elements to their children (e.g., color, font-family, line-height), while others are not (e.g., margin, border, background).
When computing an element’s style, the browser:
- Starts with the result of the cascade for that element.
- For inherited properties without an explicit value, takes the value from the parent’s computed style.
- For non-inherited properties without an explicit value, uses the property’s initial value defined by the CSS specification.
Keywords like inherit, initial, unset, and revert modify this default behavior.
8. Computing Used Values: From Relative to Absolute
After cascade and inheritance, the browser has specified values for each property. Many of these are relative and need further processing to become usable for layout and painting. This phase produces computed or used values.
Examples of this transformation include:
- Converting relative lengths (like
em,rem,%,vh,vw) into absolute pixel values, once the relevant context (font size, viewport size, containing block) is known. - Resolving
autovalues once constraints are known (e.g., auto widths inside flex or grid containers). - Calculating fallback values for features not supported in the current environment.
The output of this phase is effectively a full set of concrete style data for each element, ready to feed into layout calculations.
9. Building the Render Tree
The browser now builds a render tree, which is similar to the DOM but contains only the elements that will actually be drawn:
- Non-visual elements (like
<head>) are excluded. - Elements with
display: nonedo not appear in the render tree. - Pseudo-elements (like
::beforeand::after) that generate boxes are included.
Each node in the render tree corresponds to a box (or multiple boxes) with style information attached, including display type (block, inline, flex, grid, table, etc.). This is the structure used for layout.
10. Layout: Calculating Positions and Sizes
In the layout phase (also called reflow), the browser determines the size and position of each box in the render tree:
- The browser starts from the root element (typically
html), whose size is determined by the viewport. - It then recursively lays out child elements, using the rules of the box model and layout mode (normal flow, flexbox, grid, etc.).
- Margins, padding, borders, and positioning schemes (static, relative, absolute, fixed, sticky) are all resolved at this stage.
Layout can be costly, especially if it must be recalculated frequently (for example, when JavaScript modifies styles or DOM structure). Browsers use various optimizations to limit layout work to affected subtrees where possible.
11. Painting: Turning Boxes into Pixels
Once layout is complete, the browser knows what each element looks like and where it should appear. The next step is painting (or rasterization):
- The browser walks the render tree and draws each box in the correct order (respecting stacking contexts and z-index).
- It paints backgrounds, borders, text, shadows, images, and other visual effects.
- Complex properties like filters, blend modes, and clip paths are applied.
To improve performance, browsers often divide the page into multiple layers, painting them separately. These layers can be composited together efficiently, especially for animations and transforms.
12. Compositing and Display
The final visual result is produced in the compositing phase:
- Painted layers are combined in the correct order, taking into account stacking contexts, opacity, and transforms.
- Hardware acceleration (the GPU) is often used to composite layers quickly.
- The composed image is sent to the display system to be shown on the screen.
This step is particularly important for smooth animations. Properties that can be handled purely in the compositing stage (like transforms and opacity on isolated layers) can animate more efficiently than properties that require layout or repaint.
13. Reacting to Changes: Style, Layout, and Paint Invalidations
Web pages are dynamic. When something changes, the browser may need to redo parts of this pipeline:
- Style change: Changing a class, inline style, or stylesheet can trigger recalculation of styles for affected elements.
- Layout change: Changes that affect size or position (like content changes, font size changes, or viewport resizing) can trigger layout recalculation.
- Paint change: Visual-only changes (like color) may require repainting but not layout.
Browsers optimize by:
- Batching changes together.
- Limiting recalculations to minimal subtrees.
- Reusing styles and layout data where possible.
Understanding these distinctions helps developers write more performant CSS and JavaScript, avoiding unnecessary layout thrashing or expensive style recalculations.
14. Why This Matters for Developers
Knowing how browsers render CSS is not just academic—it directly affects how you build and optimize your sites:
- Debugging: When a style “doesn’t apply,” understanding cascade, specificity, and inheritance helps you pinpoint why.
- Performance: Minimizing costly operations (like forced synchronous layout) leads to smoother UX.
- Maintainability: Designing clear, predictable CSS architectures is easier when you know how selectors are matched and rules are resolved.
In practice, writing performant and reliable CSS means working with the browser’s rendering pipeline, not against it—keeping styles simple where possible, being mindful of selector specificity, and understanding the impact of layout and paint on runtime behavior.


