SVGs Are Amazing
A deep dive into how browsers turn SVG into pixels and why a tiny state machine inside an SVG beats heavy video for lively UIs
Why I Actually Care About This Stuff
I was building a voice agent UI and wanted something that felt alive without shipping bloated video files or gifs and lottie's paid export system pissed me off. SVG seemed perfect for the job. Tiny payload, css editable, infinitely customizable. But then I started wondering: "Wait, if it's still DOM, how is this actually different from throwing around a bunch of <div>s or just drawing on a <canvas>?"
Turns out the answer is way more interesting than I expected.
Why SVGs Are Actually Cool
Here's the thing most people miss: SVG isn't a bitmap. It's a DOM scene graph of geometry and paint instructions that gets resolved into a display list and rasterized by your GPU (via Skia on Chrome or WebRender on Firefox).
For short UI motion and icon level interaction, this can be an order of magnitude smaller than video or image sequences while staying razor sharp.
This is why stateful SVGs are perfect for doing interactive UI stuff with art.
How Your Browser Actually Renders SVG: Step by Step
Let me walk you through exactly what happens when your browser encounters an SVG. I'm going to visualize every step so you can see the magic happening.
Step 1: Parse the XML into DOM Nodes
Your browser takes xml and builds a DOM tree. Each element becomes a node with properties and attributes.
Element Tree:
Invalid XML
Step 2: Apply CSS and Resolve Units
The browser applies CSS cascade rules and converts all units to concrete pixel values. Those percentages, ems, viewport units, all become absolute pixels.
/* Input */
circle { r: 2em; fill: var(--primary-color); }
/* Resolved */
circle { r: 32px; fill: rgb(59, 130, 246); }
Step 3: Build Coordinate Transformation Matrices
Using computed styles from Step 2, the engine builds CTMs. viewBox, width, height, preserveAspectRatio, and transform are resolved to concrete numbers first, read more here.
Only elements that establish a viewport apply viewBox and preserveAspectRatio, then this mapping composes with any transform. More on transforms
Formula
CTM(element) = CTM(parent) × T(x,y) × V(viewBox, preserveAspectRatio) × M(transform)
,,]]
Step 4: Convert Everything to Paths
Here's where it gets spicy. The browser converts all shapes into path data:
- Rectangle → Four lines with optional rounded corners
 - Circle → Parametric curve equations
 - Complex arcs → Broken into cubic Bézier segments
 
Step 5: Tessellation - Breaking Paths into Triangles
GPUs draw triangles. Engines flatten vector paths to triangles or keep analytic paths until the last stage, then rasterize with GPU help. In practice, Skia and WebRender often end up with triangles for the GPU. The example below is from lyon, which turns an SVG-like path into vertex and index buffers.
Step 6: Stroke Expansion
Strokes are tricky because they're rendered as fills. The browser expands the centerline into an outline at ±width/2 along the unit normal.
Join types matter:
- Miter: Sharp corners (with miter limit math)
 - Round: Curved joins
 - Bevel: Cut corners
 
From what I've read, the broswer handles these things automatically. For example at very sharp angles a miter corner grows into a long spike. That looks ugly at small sizes, can self overlap, and will flicker when you animate geometry. The miter limit is the guardrail. When the spike gets too long the join switches to a bevel automatically. This keeps corners stable and avoids surprise artifacts. We won't be diving deep into the maths of that in this article.
Step 7: Dash Pattern Generation
For dashed lines, the browser walks the path by arc length, alternating on/off segments:
Step 8: Paint Server Resolution
Different paint types get resolved:
- Solid Colors: Simple color lookup
 - Linear Gradients: Map each pixel through the inverse gradient transform. Gradients interpolate in sRGB by default. Use 
color-interpolation="linearRGB"only when you need linear space blending. W3C id you wanna read more. - Radial Gradients: Calculate distance from center, then interpolate stops
 - Patterns: Render the pattern tile to an off screen surface, then repeat it with the pattern transform. If the tile contains vectors, the engine renders those vectors into the tile surface before repetition
 
Step 9: Clipping and Masking
clipPath defines a vector clipping region. Browsers render it as a hard clip with antialiasing at the edges for visual smoothness.
mask renders the masked subtree to an off screen surface and applies either luminance or alpha as coverage. Control this with mask-type: luminance or mask-type: alpha. The common luminance calculation is Y = 0.2126R + 0.7152G + 0.0722B.
Step 10: Filter Graph Processing
Each filter primitive runs in its own pass over offscreen surfaces. Gaussian blur uses separable convolution:
Step 11: Display List Generation
The browser builds optimized draw commands:
- Fill triangles with paint
 - Apply transforms
 - Composite with blending
 
Step 12: GPU Rasterization
Finally, triangles get rasterized with anti-aliasing. The GPU samples coverage at sub-pixel precision:
Wrapping Up
SVGs aren't just "scalable images", they're a sophisticated graphics pipeline that your browser optimizes heavily. Know the pipeline, choose your animations wisely, and you'll build interfaces that feel magical but run efficiently.
Your users will thank you.
Wrapping it a bit quicker near the end because I spent an unhealthy amount of time understanding the whole process as I was writing down this post. Absolutely fascinating and inspiring to see a glimpse of the magic that happens behind the scenes.