DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

SVG by Hand: Why Every Developer Should Understand the Markup

I used to treat SVGs as black boxes. Figma exported them, I dropped them into the project, and I never looked at the markup. Then one day a client's logo had a 4MB SVG file that was crashing the mobile browser. I opened it and found 800 lines of nested groups, invisible layers, duplicate gradient definitions, and enough metadata to fill a novel. I cleaned it down to 12KB by hand. That's when I started learning SVG as a language, not just a file format.

SVG is XML. It's readable, writable, and manipulable as text. You can inline it in HTML, style it with CSS, animate it with JavaScript, and version-control it with git. No other image format offers that. Here's the practical knowledge that matters.

The viewport and viewBox

Every SVG starts with these two concepts:

<svg width="200" height="200" viewBox="0 0 100 100">
  <!-- content -->
</svg>
Enter fullscreen mode Exit fullscreen mode

The width and height set the physical size on the page. The viewBox defines the internal coordinate system. In this example, the SVG is 200 pixels wide on screen, but internally it uses a 100x100 coordinate system. Everything you draw is positioned in viewBox coordinates, and the browser scales it to fit the physical dimensions.

This is what makes SVGs resolution-independent. The viewBox doesn't have units. It's an abstract coordinate space. Whether the SVG renders at 200px or 2000px, the shapes are defined in the same coordinate system and scale smoothly.

If you omit the viewBox, the internal coordinates match the width and height, and you lose the ability to scale freely. Always include a viewBox.

The basic shapes

SVG has seven primitive shapes, and you can build anything with them:

<!-- Rectangle -->
<rect x="10" y="10" width="80" height="60" rx="5" fill="#6C5CE7"/>

<!-- Circle -->
<circle cx="50" cy="50" r="40" fill="#00B894"/>

<!-- Ellipse -->
<ellipse cx="50" cy="50" rx="40" ry="25" fill="#FDCB6E"/>

<!-- Line -->
<line x1="10" y1="10" x2="90" y2="90" stroke="#2D3436" stroke-width="2"/>

<!-- Polyline (open shape) -->
<polyline points="10,90 50,10 90,90" fill="none" stroke="#E17055" stroke-width="2"/>

<!-- Polygon (closed shape) -->
<polygon points="50,10 90,90 10,90" fill="#74B9FF"/>

<!-- Text -->
<text x="50" y="50" text-anchor="middle" font-size="16">Hello</text>
Enter fullscreen mode Exit fullscreen mode

The coordinates are all relative to the viewBox. The fill attribute colors the interior; stroke colors the outline. These can be CSS color values, and they can be set via CSS classes instead of attributes.

Paths: the universal shape

The <path> element can draw anything. Its d attribute contains a series of commands:

<path d="M 10 30 L 90 30 L 50 80 Z" fill="#6C5CE7"/>
Enter fullscreen mode Exit fullscreen mode

The commands:

  • M x y: Move to a point (pen up)
  • L x y: Line to a point (pen down)
  • H x: Horizontal line
  • V y: Vertical line
  • C x1 y1 x2 y2 x y: Cubic Bezier curve
  • S x2 y2 x y: Smooth cubic Bezier
  • Q x1 y1 x y: Quadratic Bezier curve
  • A rx ry rotation large-arc sweep x y: Arc
  • Z: Close the path

Lowercase versions use relative coordinates. So m 10 30 means "move 10 right and 30 down from the current position."

Most exported SVGs are entirely <path> elements. Figma, Illustrator, and Inkscape all convert shapes to paths on export. This is fine for complex artwork, but for simple icons, the primitive shapes produce cleaner, more readable code.

CSS and SVG work together

SVG attributes like fill, stroke, stroke-width, and opacity can be set via CSS:

.icon {
  fill: currentColor;
  stroke: none;
  width: 24px;
  height: 24px;
}

.icon:hover {
  fill: #6C5CE7;
  transition: fill 0.2s;
}
Enter fullscreen mode Exit fullscreen mode

fill: currentColor is the most useful trick for SVG icons. It makes the icon inherit the text color of its parent, so a single SVG works across your entire site regardless of the surrounding color scheme.

Optimization matters

Design tools export verbose SVGs. Here's what to remove:

  1. Editor metadata. Illustrator and Inkscape add comments, namespace declarations, and generator information. All of it can be removed.

  2. Unnecessary groups. Export often wraps shapes in <g> elements with identity transforms (transform="translate(0,0)"). These add nesting and file size without visual effect.

  3. Hidden layers. Elements with display="none" or visibility="hidden" that were invisible in the design tool but still present in the export.

  4. Redundant precision. Path coordinates exported to six decimal places (d="M 12.546875 34.123456") can usually be rounded to one or two decimals without visible difference.

  5. Inline styles vs attributes. Some tools export style="fill:#000" instead of fill="#000". The attribute form is shorter and works identically.

SVGO (SVG Optimizer) automates most of this:

npx svgo input.svg -o output.svg
Enter fullscreen mode Exit fullscreen mode

For complex files, SVGO routinely cuts file size by 50-80%. For simple icons, hand-optimization often produces smaller files because you can replace paths with primitives and remove transformations entirely.

Accessibility

SVGs need accessibility markup. At minimum:

<svg role="img" aria-labelledby="title-id" viewBox="0 0 100 100">
  <title id="title-id">Company Logo</title>
  <!-- shapes -->
</svg>
Enter fullscreen mode Exit fullscreen mode

For decorative SVGs (visual flourishes, background patterns), use aria-hidden="true" to hide them from screen readers:

<svg aria-hidden="true" viewBox="0 0 100 100">
  <!-- decorative shapes -->
</svg>
Enter fullscreen mode Exit fullscreen mode

Inline SVG vs img tag vs CSS background

There are three ways to use SVGs in HTML, each with trade-offs:

Inline SVG (<svg> directly in HTML): Full CSS control, can be styled and animated, adds to DOM size, can't be cached separately from the HTML.

Img tag (<img src="icon.svg">): Cached as a separate file, no CSS control over fills and strokes, simpler to manage.

CSS background (background-image: url(icon.svg)): Cached, no CSS fill control, good for decorative patterns.

For icons that need to respond to color themes, inline SVG with fill: currentColor is the best approach. For static images and illustrations, the img tag gives you caching benefits.

For quick SVG editing, optimization, and viewBox adjustments without opening a full design tool, I built an SVG editor at zovo.one/free-tools/svg-editor that lets you paste SVG code, edit it visually, and copy the cleaned-up result.

SVG is the only image format that's also a markup language. Understanding the underlying code gives you control that no export button provides -- you can optimize by hand, debug rendering issues by reading the source, and build dynamic graphics that respond to user interaction. It's worth learning.


I'm Michael Lip. I build free developer tools at zovo.one. 350+ tools, all private, all free.

Top comments (0)