Drawing With Code: An SVG Primer for Web Developers Skip to main content

Drawing With Code: An SVG Primer for Web Developers

A hands-on SVG primer for web developers. Learn inline SVG, the core shapes, viewBox, fill and stroke, and stroke-dasharray animations — with interactive playgrounds at every step.

There's a drawing language hiding inside HTML, and you've been looking at it every day without really seeing it. The loading spinner on your dashboard. The icons in your navbar. The underline that stretches when you hover a link. The checkmark that draws itself after you submit a form. All of them are SVG, quietly doing the work.

And yet — if you've ever copy-pasted an SVG from Figma and stared at the markup wondering what half of it does, you're in good company. Most frontend developers have shipped plenty of SVG without ever writing one by hand.

Let's fix that. In about fifteen minutes, you'll have the mental model to read SVG, write it from scratch, and animate it with the CSS you already know.

<svg width="160" height="160" viewBox="0 0 160 160">
  <circle 
    cx="80" cy="80" r="50" fill="tomato" 
    class="shape s1" />
  <rect 
    width="100" height="100"
    x="30" y="30" rx="12" fill="mediumpurple" 
    class="shape s2" />
  <polygon 
    points="80,20 140,140 20,140" 
    fill="goldenrod" 
    class="shape s3" />
</svg>

The whole thing condenses to a single sentence: SVG is DOM. Every <circle>, every <line>, every <path> is a real DOM element. querySelector finds it. CSS selectors style it. addEventListener hears its events. That one fact is the reason SVG is fun to work with once you've seen it.

Hello, SVG

What is SVG? It's an XML-based markup language for two-dimensional graphics. Unlike JPG or PNG — opaque binary blobs that would look like gibberish in a text editor — SVG is plain text. You can write it by hand, paste it into your HTML, and every shape shows up on the page.

Here's the smallest SVG that still feels like something:

Your first SVG — edit and watch it render

A red circle, 100 pixels wide, positioned at the center of a 160×160 canvas. Six lines of markup, no libraries, no canvas context, no build step. Drop that into any HTML file right now and it works.

You could save that SVG to a file and load it with <img src="...">, and sometimes that's the right call. But pulling SVG into an <img> treats it like any other image format, which misses most of the fun.

Here's the pattern that sells inline SVG to most developers the first time they see it — hover the shape and watch CSS transition the radius and fill of a live DOM element:

<svg class="icon" width="160" height="160">
  <circle cx="80" cy="80" r="50" />
</svg>
Hover to see CSS transition SVG attributes

No JavaScript. No canvas redraw. Just CSS transitioning attributes on a DOM element that happens to be a circle instead of a div. That's the promise: illustrations with the full power of the web platform behind them.

Once you start looking, you'll see SVG everywhere — icon systems, charts, maps, logos, loaders, illustrations. It's the quiet foundation of modern UI graphics.

The shapes you'll actually use

How do you draw shapes in SVG? Use one of six geometric primitives — <line>, <rect>, <circle>, <ellipse>, <polygon>, and <path> — and set attributes that describe their geometry.

Where HTML gives you <p> and <h1> for documents, SVG gives you <circle> and <rect> for drawings. The first five primitives handle about 80% of UI graphics. <path> is the universal escape hatch — a subject worth an article of its own.

We'll spend the rest of this section with the first five.

Lines

The simplest shape in the language. Two points, one line between them. Move each endpoint with the sliders:

index.html
<svg width="240" height="160">
  <line
    x1="20" y1="140"
    x2="220" y2="20"
    stroke="#f39c12"
    stroke-width="3" />
</svg>
(x₁; y₁) (x₂; y₂)
Line endpoints — drag the sliders

Four numbers — x1, y1, x2, y2 — and SVG handles the rest. Try drawing a diagonal line in pure HTML and you'll end up with a thin <div>, transform: rotate(), absolute positioning, and a calculator to work out the angle. SVG just asks "where does it start, where does it end?"

Rectangles

Rectangles position from the top-left corner with x and y, size themselves with width and height, and optionally round their corners with rx:

<svg width="260" height="180">
  <rect class="r" />
</svg>
w h sw
Rectangle — drag the sliders

Feels like a styled <div> until you run into two things that catch everyone the first time.

Circles

A center and a radius. Three numbers, one satisfying primitive:

<svg width="200" height="160">
  <circle class="c" />
</svg>
r
Circle — centre + radius + colour

Hard to mess up a circle. cx/cy move the whole thing; r resizes it.

Ellipses

A circle with a second radius. rx controls horizontal extent, ry controls vertical:

<svg width="300" height="160">
  <ellipse class="e" />
</svg>
rx ry
Ellipse — centre + two independent radii

Notice fill-opacity. It's a dedicated attribute that makes the interior semitransparent without touching the stroke. In CSS you'd reach for rgba() on both background and border-color and still lack the same granularity. SVG's attribute surface is genuinely richer than what CSS exposes — every visual property can be tuned independently of every other.

Polygons

A list of points. SVG connects them in order and closes the shape automatically. Move each vertex with the sliders:

index.html
<svg width="240" height="200">
  <polygon points="120,10 230,190 10,190"
           fill="#e74c3c" fill-opacity="0.15"
           stroke="#e74c3c" stroke-width="2"
           stroke-linejoin="round" />
</svg>
1 2 3
Triangle — move each of three points

Polygons don't have to be equilateral. Any set of points works — irregular, concave, star-shaped, self-intersecting. Badges, arrows, custom frames: all polygon territory.

For regular polygons — hexagons, octagons, stars — you need a pinch of trigonometry to space the vertices evenly around a circle. Slide sides from 3 to 12:

index.html
<svg width="300" height="200">
  <polygon 
    points="150,40 201,70 201,130 150,160 99,130 99,70"
    fill="#1abc9c" fill-opacity="0.2"
    stroke="#1abc9c" stroke-width="2"
    stroke-linejoin="round" />
</svg>
r
Regular N-gon — slide 'sides' from 3 to 12

That "pinch of trigonometry" is two functions: cos(angle) gives the x of a point on a circle, sin(angle) gives the y. Stepping angle from 0 to 2π in n equal slices places n vertices around the circle. But angle 0 in trigonometry points to the right, so without any offset the first vertex would sit on the circle's right edge. The -Math.PI / 2 you'll see in the code below shifts that start a quarter-turn to the top.

Edit the regularPolygon function itself

Making SVGs actually scalable

What is viewBox in SVG? It's a single attribute that defines an internal coordinate system, independent of the SVG element's rendered pixel size. It's the feature that makes SVGs truly responsive — and the reason PNG icons look fuzzy on retina displays while SVG icons stay crisp at any zoom level.

Every shape we've drawn so far used hardcoded pixel values. That's fine when the container never changes size, but it breaks the moment someone opens your page on a different screen. The fix is one attribute — pan and zoom a single rect by changing only its container's viewBox:

index.html
<svg viewBox="0 0 300 200" width="100%" height="200"
     style="background:#faf7ff; border:1px solid #e0d9f0;">
  <rect x="50" y="30" width="200" height="140"
        fill="#9b59b6" fill-opacity="0.2"
        stroke="#9b59b6" stroke-width="3" rx="8" />
</svg>
(0,0) (50,30) (300,200)
viewBox — pan and zoom the same shape

viewBox takes four numbers — origin-x origin-y width height. Here's the mental model that stuck with me: think of the <svg> element as a printed map, and viewBox as a sliding rectangle that frames a region of that map. The four numbers say "show this much of the world, from this corner, rendered at whatever pixel dimensions the element happens to have."

When viewBox dimensions match the element's pixel size, you get 1 rendering. Halve the viewBox and everything appears at 2×. Double it and everything shrinks to half.

The first two values — the origin — pan the view. Set them to -50 -50 and you shift the visible region up and left by 50 units, revealing content that was previously off-screen. Useful for mini-maps, pan-and-zoom UIs, or any time you want to frame a region of a larger drawing.

Here's the payoff. Same image on both sides, both shown at a heavy upscale — drag the divider:

96 px PNG
96 px PNGSVG · vector
viewBox vs viewport — two words, two things

SVG uses both terms, and they mean different things:

  • The viewport is the outer rectangle — the rendered size, determined by width and height on the <svg> element.
  • The viewBox is the region of content mapped into that viewport.

Viewport = outer pixel box. viewBox = inner coordinate box. Two words for two ideas. Thanks, SVG spec.

In practice, most developers set viewBox once at the top of the file and never touch it again. The payoff is huge: the SVG now scales like any other image, without quality loss, at any size, on any screen, forever.

Fill, stroke, and the rest

Every SVG shape has two paintable surfaces — the interior (fill) and the outline (stroke). Both can be set directly in markup or overridden in CSS. They're fully interchangeable:

.diagram rect {
  fill: #16213e;
  stroke: #e74c3c;
  stroke-width: 2;
}

CSS background and border will feel like the natural analogies — but SVG strokes expose a properties surface CSS borders can't match:

PropertyWhat it controls
strokeColor
stroke-widthThickness
stroke-dasharrayDash pattern — alternating dash and gap lengths
stroke-linecapEnd caps: butt, round, square
stroke-linejoinCorner joins: miter, round, bevel
stroke-dashoffsetSlides the dash pattern along the outline
stroke-opacityTransparency of the stroke, independent of fill

Seven properties of fine-grained control. CSS borders give you maybe two. Flip through each one:

stroke — colour of the outline. Any CSS colour value works.

<polygon ... stroke="#e74c3c" stroke-width="3" />
index.html
<svg width="180" height="120">
  <polygon 
    points="80,15 120,15 140,55 120,95 80,95 60,55"
    fill="none" stroke="#e74c3c" 
    stroke-width="3" />
</svg>
stroke — colour of the outline

Mix every stroke property at once and see what each one does:

<svg width="220" 
  height="140" 
  viewBox="0 0 220 140">
  <polygon 
    class="hex" 
    points="80,15 120,15 140,55 120,95 80,95 60,55" 
    fill="none" />
</svg>
Stroke explorer — mix every stroke property at once

The stroke-dashoffset trilogy

How does stroke-dasharray work? It defines an alternating pattern of dash and gap lengths along a path's outline. Paired with stroke-dashoffset — which slides that pattern along the shape — it unlocks three of the most recognizable animations on the web.

stroke-dashoffset is the single property that makes SVG animations click.

You've definitely seen all three. Here's how each one is built.

Marching ants

Small dashes cycling endlessly around a selection box — the effect you see in image editors when you lasso a region:

<svg width="180" height="140">
  <rect 
    class="selection" 
    x="30" y="20" 
    width="120" height="100"
    fill="none" stroke="#f39c12" 
    stroke-width="2" rx="2" />
</svg>
Marching ants — CSS-variable driven

The trick: set stroke-dashoffset to negative the total dash + gap length for a perfectly seamless loop. The pattern slides exactly one period and arrives at a position indistinguishable from where it started.

Loading spinner

The spinner every SaaS app ships. Two animations collaborate — one grows the visible dash, another rotates the offset around the circle:

<svg width="100" height="100">
  <circle  
    class="spinner" 
    cx="50" cy="50" 
    r="40" fill="none" />
</svg>
Loading spinner — two animations in concert

The dash itself breathes in and out, while its position rotates around the circle. Layered against a still circle, you get the classic indeterminate spinner.

Self-drawing path

The showstopper. Every landing page's hero animation. The checkmark that draws itself after your form submits. A logo revealing on page load.

Hide a full-length dash behind a matching offset, then animate the offset to zero — even for a complex single-stroke figure traced in one continuous contour:

<svg width="430" height="430" viewBox="0 0 3841 3841">
  <path
    class="stroke"
    pathLength="100"
    d="M 2.6 7384.9 … (quite a lot of points) … "
    fill="none"
    stroke-width="3"
    stroke-linejoin="round"
    stroke-linecap="round" />
</svg>
Self-drawing path — auto-replays on a loop

The whole silhouette — head, body, legs — is one continuous <path> that never lifts the pen, which is the prerequisite for the dashoffset trick to feel like a single drawn line. Add pathLength="100" and the browser scales the dash math against your declared unit instead of the real arc length, so you never have to chase the magic number with element.getTotalLength(). This pattern — the self-drawing SVG animation — appears on thousands of product sites. A handful of lines of CSS. No dependencies.

The rest of the iceberg

What we covered — the primitives, viewBox, fill, stroke, and the stroke-dasharray animations — gets you about 80% of real-world SVG work. The remaining 20% lives in features that each deserve their own primer:

  • <path> — the universal shape. Cubic Bezier curves, arcs, freeform outlines. Anything you can draw in Illustrator, <path> expresses as a compact string.
  • <clipPath> and <mask> — cut shapes out of other shapes, or use grayscale images to control transparency pixel-by-pixel.
  • <filter> — Gaussian blurs, drop shadows, color matrix transforms, displacement maps. A full compositing pipeline inside your markup.
  • Gradients<linearGradient> and <radialGradient> are first-class fill values. Define once, reuse anywhere.
  • <use> and <symbol> — define a shape template once and stamp it out repeatedly. The SVG way to build an icon system without duplicating markup.
  • <foreignObject> — embed arbitrary HTML inside an SVG, for when you need vector rendering and text selection and form inputs in a single canvas.
When to use inline SVG vs img tag

Use <img src="foo.svg"> when the graphic is purely decorative, never needs to be styled by your site's CSS, and is large enough that HTTP caching it separately is worthwhile.

Use inline SVG — markup pasted directly into your HTML — when you need to style it with CSS, target it with JavaScript, animate individual elements, or respond to user interaction. For most interactive UI work, inline SVG is the right call.

Remember the thesis: SVG is DOM. Every primitive you just learned is a real DOM node. document.querySelector finds them. CSS selectors match them. addEventListener listens to them. getBoundingClientRect measures them. It's one rendering system the whole way down.

Loading sandbox…
Your turn — sketch something

Built with SVG in mind at Obox Systems

This article is part of the Obox Systems engineering blog — where we share what we learn shipping interactive UIs, data visualizations, and custom design systems for product teams.

If SVG-rich work is on your roadmap, let's talk — we're always up for a conversation about craft. More primers on web platform fundamentals, frontend architecture, and AI-augmented development coming soon.