Here's a confession: I avoided SVG for years. The XML syntax looked intimidating, viewBox made zero sense, and I figured PNG icons were "good enough." Then I had to build a dashboard with animated charts that needed to look sharp on 4K screens. PNGs were not good enough. SVG saved the project — and completely changed how I think about graphics on the web.
If you've been avoiding SVG or just copy-pasting it without understanding what's going on, this guide is for you. We're going from zero to confident.
Inline SVG vs. <img> — Which to Use?
You can use SVG two main ways on the web, and the choice matters more than most people think.
Using SVG as an <img>
<img src="icon.svg" alt="Settings icon" width="24" height="24">
This treats the SVG like any other image. The browser fetches, caches, and renders it. Simple. But you lose the ability to style it with CSS or interact with its internal elements. It's a black box.
Inline SVG (Directly in HTML)
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7l10 5 10-5-10-5z" fill="currentColor"/>
<path d="M2 17l10 5 10-5" stroke="currentColor"
stroke-width="2" fill="none"/>
</svg>
Inline SVG lives in the DOM. You can target any element inside it with CSS, animate it, change colors on hover, and manipulate it with JavaScript. The tradeoff? It adds to your HTML weight and can't be cached separately.
My rule of thumb: Use <img> for decorative images and illustrations that don't need interaction. Use inline SVG for icons, logos, and anything you want to style or animate. For icon systems, I prefer a sprite sheet with <use> references — best of both worlds.
Understanding viewBox — The Key to Everything
This is where most people get confused and give up. Let me demystify it.
The viewBox attribute defines the coordinate system inside the SVG. It doesn't set the size of the SVG on the page — that's what width and height do. viewBox defines the "camera window" looking at your drawing.
<svg width="200" height="200" viewBox="0 0 100 100"> <circle cx="50" cy="50" r="40" fill="#3b82f6"/> </svg>
Here's what's happening: the SVG displays at 200×200 pixels on screen, but internally it uses a 100×100 coordinate grid. The circle is centered at (50, 50) in that internal grid, with a radius of 40 units. Because the display size is 2× the viewBox size, everything is scaled up by 2×.
The four viewBox numbers are: min-x min-y width height.
- min-x, min-y — where the camera starts (top-left corner)
- width, height — how much of the internal coordinate space is visible
Think of viewBox like a map zoom level. A smaller viewBox width/height zooms in (shows less, things look bigger). A larger viewBox width/height zooms out (shows more, things look smaller). The min-x and min-y values pan the camera around.
Making SVG Responsive
Want an SVG that scales to fit its container? Remove the width and height attributes and just keep viewBox:
<svg viewBox="0 0 100 100" class="responsive-svg">
<!-- shapes here -->
</svg>
<style>
.responsive-svg {
width: 100%;
height: auto;
}
</style>
The SVG now stretches to fill its container width while maintaining the aspect ratio defined by viewBox. This is how most icon systems work.
Basic Shapes — Your Building Blocks
SVG gives you a handful of primitive shapes. You'd be surprised how much you can build with just these:
<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
<!-- Rectangle -->
<rect x="10" y="10" width="80" height="60"
rx="8" fill="#3b82f6"/>
<!-- Circle -->
<circle cx="160" cy="40" r="30" fill="#22c55e"/>
<!-- Ellipse -->
<ellipse cx="280" cy="40" rx="50" ry="30" fill="#f59e0b"/>
<!-- Line -->
<line x1="10" y1="120" x2="390" y2="120"
stroke="#e2e8f0" stroke-width="2"/>
<!-- Polyline (open shape) -->
<polyline points="10,200 80,150 150,220 220,160"
fill="none" stroke="#8b5cf6" stroke-width="3"/>
<!-- Path (the ultimate shape) -->
<path d="M 300 150 Q 350 100 390 150 T 390 250"
fill="none" stroke="#ef4444" stroke-width="2"/>
</svg>
The <path> element is where SVG gets powerful — and complex. The d attribute uses a mini-language of commands: M (move to), L (line to), C (cubic bezier), Q (quadratic bezier), A (arc), and Z (close path). You'll rarely write path data by hand — tools like Figma and Illustrator export it for you.
Styling SVG with CSS
This is where inline SVG really shines. You can style SVG elements with CSS just like HTML elements — but the property names are different.
/* Key SVG CSS properties */
.icon path {
fill: #64748b; /* like background-color */
stroke: #0f172a; /* border color */
stroke-width: 2; /* border width */
stroke-linecap: round; /* line endings */
stroke-linejoin: round; /* corner style */
opacity: 0.8;
}
/* Change color on hover */
.icon:hover path {
fill: #3b82f6;
transition: fill 0.2s ease;
}
/* Use currentColor to inherit text color */
.icon path {
fill: currentColor;
}
The currentColor trick is incredibly useful. Set fill="currentColor" on your SVG paths, and the icon automatically matches whatever text color its parent has. Change the parent's color and the icon follows. I use this for every icon system.
CSS Classes on SVG Elements
<svg viewBox="0 0 24 24">
<circle class="bg" cx="12" cy="12" r="10"/>
<path class="check" d="M8 12l2.5 2.5L16 9"/>
</svg>
<style>
.bg { fill: #dcfce7; }
.check {
fill: none;
stroke: #16a34a;
stroke-width: 2;
stroke-linecap: round;
}
</style>
Animating SVG
SVG animations can be dead simple or incredibly sophisticated. Let's start simple.
CSS Transitions and Animations
/* Pulse animation on a circle */
.pulse-dot {
transform-origin: center;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { r: 8; opacity: 1; }
50% { r: 12; opacity: 0.6; }
}
/* Line drawing effect */
.draw-line {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: draw 1.5s ease forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
The line-drawing effect is one of my favorites. It works by setting stroke-dasharray to the path's total length (creating one big dash), then animating stroke-dashoffset from that length to 0. The path appears to draw itself.
Finding the path length: Use JavaScript — document.querySelector('path').getTotalLength() — to get the exact length for stroke-dasharray. Or just estimate with a large number; if it's bigger than the actual length, the effect still works.
SVG SMIL Animations (Built-in)
SVG has its own animation system called SMIL. It works without CSS or JavaScript:
<circle cx="50" cy="50" r="20" fill="#3b82f6">
<animate attributeName="r"
values="20;30;20"
dur="2s"
repeatCount="indefinite"/>
<animate attributeName="fill"
values="#3b82f6;#8b5cf6;#3b82f6"
dur="2s"
repeatCount="indefinite"/>
</circle>
SMIL is powerful and works directly in the SVG markup. However, browser support is solid everywhere except — you guessed it — older versions of IE (which aren't really a concern anymore). For anything complex, I prefer CSS animations or a library like GreenSock (GSAP).
SVG Accessibility — Don't Skip This
Accessibility is where many developers fall short with SVG. An SVG without proper attributes is invisible to screen readers.
For Decorative SVGs
<!-- Hide from screen readers entirely --> <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24"> <path d="..." fill="currentColor"/> </svg>
For Meaningful SVGs
<svg role="img" aria-labelledby="icon-title" viewBox="0 0 24 24"> <title id="icon-title">Shopping cart with 3 items</title> <path d="..." fill="currentColor"/> </svg>
The <title> element inside SVG acts like alt text for images. Use role="img" to tell assistive technology that this SVG represents a single image. Connect them with aria-labelledby.
For complex graphics (charts, diagrams), add a <desc> element with a longer description:
<svg role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">Monthly Revenue</title>
<desc id="chart-desc">
Bar chart showing revenue from January to June 2026,
with a peak of $48,000 in March.
</desc>
<!-- chart elements -->
</svg>
Practical Pattern: Icon Sprite Sheet
Instead of inlining every icon, create a sprite file and reference icons with <use>:
<!-- icons.svg (loaded once, cached) -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0
001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1
1h-3m-4 0h4" fill="none" stroke="currentColor"
stroke-width="2"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="8" fill="none"
stroke="currentColor" stroke-width="2"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"
stroke="currentColor" stroke-width="2"/>
</symbol>
</svg>
<!-- Usage anywhere in the page -->
<svg class="icon" aria-hidden="true">
<use href="#icon-home"/>
</svg>
<svg class="icon" aria-hidden="true">
<use href="#icon-search"/>
</svg>
This approach gives you the styling benefits of inline SVG with much cleaner HTML. Define once, use everywhere. The currentColor trick works perfectly with <use> references too.
Convert SVG Layouts to Images
Built a beautiful SVG graphic? Convert your HTML + SVG layouts into crisp PNG, JPG, or WebP images instantly.
Try HTML to PNG →SVG Optimization — Keep It Lean
SVGs exported from design tools are bloated. They contain editor metadata, unnecessary groups, decimal precision you don't need, and redundant attributes. Always optimize before shipping.
Use SVGOMG (a web-based tool) or svgo on the command line:
npx svgo icon.svg -o icon.min.svg
A typical Figma export might shrink by 40-60% after optimization. That adds up fast when you have 50+ icons.
When NOT to Use SVG
SVG isn't always the answer. Here's when I reach for something else:
- Photos or complex raster images — SVG represents vector shapes; photographs should be JPEG/WebP
- Extremely complex illustrations with thousands of paths — the DOM overhead can hurt performance, and a well-compressed PNG might be smaller
- Simple solid-color icons at fixed sizes — an icon font or even well-optimized PNGs might be simpler (though SVG is still my default)
SVG is excellent for icons, logos, simple illustrations, charts, data visualizations, UI elements, and anything that needs to scale without losing quality. Once you start using it properly, you'll find it popping up everywhere in your workflow — and wondering how you ever got by without it.