The Developer's Quarterly · Browser RenderingVol. I · January 2026

What Is Browser Paintingand How Does It Work?

If you've ever wondered what actually happens between React calling setState and something appearing on your screen, the answer lives in the browser's rendering pipeline and painting is its final, visible act.

If you have ever wondered what actually happens between React calling setState and something appearing on your screen, the answer lives in the browser's rendering pipeline and painting is its final, visible act. Understanding it will change how you think about performance, animations, and hooks like useLayoutEffect.

The Pipeline Before the Paint

Painting does not happen in isolation. It sits at the end of a sequence the browser runs every time something visual needs to change.

Pipeline
Parse -> Style -> Layout -> Paint -> Composite

When your React component renders and the DOM updates, the browser kicks off this chain. First it recalculates styles, then it runs layout, also called reflow, to figure out the size and position of every element on the page. Only after those two steps does the browser move on to painting.

What painting actually means

In the painting phase, the browser converts each box calculated during layout into actual pixels on the screen. This includes drawing text, colors, borders, shadows, and replaced elements like buttons and images.

The browser does not produce one giant image in one pass, though. Drawing to the screen is generally broken into several layers so repainting can be done even faster than the initial paint. Elements that use opacity, transform, or will-change often get promoted to their own GPU layer.

After layers are painted, the final step, compositing, assembles them in the correct stacking order to produce the frame you see.

The three paths through the pipeline

1 Layout change

The browser must reflow, repaint, and composite.

  • Examples: width, height, top
  • Most expensive path

2 Paint-only change

Layout is skipped, but the browser still repaints and composites.

  • Examples: background-color, box-shadow
  • Middle cost path

3 Composite-only change

Layout and paint are skipped and the browser jumps straight to compositing.

  • Examples: transform, opacity
  • Fastest animation path

This is why performance-conscious React developers prefer animating transform over left or top, and opacity over display.

Where React fits in

React batches DOM mutations and commits them all at once, but the browser still has to run its own pipeline after every commit.

React timing
import { useRef, useState, useLayoutEffect, useEffect } from 'react'; function AnimatedBox() { const boxRef = useRef(null); const [width, setWidth] = useState(0); useLayoutEffect(() => { const measured = boxRef.current.getBoundingClientRect().width; setWidth(measured); }, []); useEffect(() => { console.log('Browser has painted. Width was:', width); }, [width]); return ( <div ref={boxRef} style={{ width: '50%', background: 'steelblue' }}> Rendered width: {width}px </div> ); }

The key distinction is that useLayoutEffect fires synchronously before paint, while useEffect fires after paint. Doing expensive layout measurements in useEffect can cause a visible flash. useLayoutEffect prevents this by acting before any pixels are committed to screen.

Animation cost
// Expensive: triggers layout and paint on every frame <div style={{ left: `${x}px` }} /> // Cheap: compositor-only, skips layout and paint <div style={{ transform: `translateX(${x}px)` }} />

The 16ms budget

To ensure smooth scrolling and animation, everything on the main thread, including calculating styles, reflow, and paint, must take less than 16.67ms to fit a 60fps frame budget. Miss it and you get dropped frames, or jank.

How browsers differ

All three major browser engines share the same conceptual pipeline, but their implementations diverge under the hood. Chrome, Edge, and Opera run on Chromium and Blink. Firefox uses Gecko. Safari is built on WebKit.

Chrome and Edge Rasterization is primarily CPU-driven before frames are sent to the GPU.
Firefox WebRender pushes more vector drawing directly onto the GPU.
Safari Core Animation layers favor battery-efficient compositing on Apple hardware.
Real-world effect Font rendering, CSS support, and GPU layer promotion can vary across engines.

What this means for your React code

Use transform and opacity for animations to stay on the compositor thread. Measure DOM geometry in useLayoutEffect, not useEffect, to avoid flicker. Avoid reading layout properties like offsetHeight inside loops because it forces the browser to flush layout repeatedly.

Once you have a mental model of the pipeline, performance problems stop feeling like mysterious glitches and start looking like predictable outcomes of the steps you ask the browser to take.