SVG for Web Developers: A Practical Guide
Learn how to create, optimize, and animate SVG graphics for the web.
What Makes SVG Different from Other Image Formats
When you embed a PNG or JPEG on a page, you're working with a fixed grid of pixels. Zoom in far enough and you see the individual squares. SVG works differently — it stores mathematical descriptions of shapes, so a circle is literally <circle cx="50" cy="50" r="40"/> rather than thousands of colored dots. That means your graphics stay crisp at any resolution, from a 320px mobile screen to a 4K monitor.
For web developers, this has practical consequences. Icons, logos, charts, and illustrations that would need multiple @2x and @3x raster exports can ship as a single SVG file. You can also target SVG elements with CSS and JavaScript just like regular HTML — making interactive and animated graphics far more maintainable.
This svg tutorial covers everything from basic shapes to animation, optimization, and performance best practices.
Core SVG Elements and Attributes
Every SVG starts with the <svg> root element. The most important attribute to set immediately is viewBox, which defines the internal coordinate system. Without it, your graphic won't scale correctly when you resize the element.
<svg viewBox="0 0 100 100" width="100" height="100" role="img" aria-label="Simple shapes demo">
<title>Shapes demo</title>
<!-- Rectangle -->
<rect x="10" y="10" width="30" height="20" fill="#4f46e5" rx="3"/>
<!-- Circle -->
<circle cx="70" cy="20" r="15" fill="#06b6d4"/>
<!-- Triangle via polygon -->
<polygon points="50,60 20,90 80,90" fill="#10b981"/>
<!-- Complex path: rounded arrow -->
<path d="M 10 75 L 40 75 L 40 65 L 55 80 L 40 95 L 40 85 L 10 85 Z"
fill="#f59e0b"/>
</svg>
The viewBox="0 0 100 100" declaration means the internal canvas runs from (0,0) to (100,100). The width and height attributes control how much space the SVG occupies in your layout — always set them explicitly to prevent Cumulative Layout Shift (CLS), which tanks your Core Web Vitals scores.
The <path> Element
<path> is the Swiss Army knife of SVG. Its d attribute accepts a mini-language of commands:
| Command | Meaning |
|---|---|
M x y |
Move to (x, y) without drawing |
L x y |
Draw a line to (x, y) |
C x1 y1 x2 y2 x y |
Cubic Bézier curve |
A rx ry rotation large-arc sweep x y |
Arc |
Z |
Close path back to start |
Lowercase versions (m, l, c) use relative coordinates. You'll rarely write complex paths by hand — tools like Figma or Illustrator export them — but understanding the commands helps you debug and optimize.
Grouping with <g> and Reusing with <symbol>
Wrap related elements in a <g> tag to apply transforms or styles collectively:
<svg viewBox="0 0 200 80" width="200" height="80">
<!-- Group: rotated and colored as a unit -->
<g transform="translate(100, 40) rotate(45)" fill="#4f46e5">
<rect x="-20" y="-5" width="40" height="10"/>
<rect x="-5" y="-20" width="10" height="40"/>
</g>
</svg>
Inline SVG vs. External SVG
There are four main ways to use SVG on the web, and choosing the right one matters for performance and functionality.
| Method | CSS Styling | JS Access | Cached | Use Case |
|---|---|---|---|---|
Inline <svg> |
✅ Full | ✅ Full | ❌ No | Critical icons, animations |
<img src="icon.svg"> |
❌ Limited | ❌ No | ✅ Yes | Static images |
<object> |
✅ Internal only | ⚠️ Limited | ✅ Yes | Complex standalone graphics |
CSS background-image |
❌ No | ❌ No | ✅ Yes | Decorative backgrounds |
Inline SVG gives you the most power. You can style paths with CSS classes, animate elements, and attach event listeners. The tradeoff is that inline SVGs aren't cached by the browser separately from your HTML. Keep critical above-fold inline SVGs under 4KB.
SVG sprites give you the best of both worlds for icon sets. Define all your icons once in a hidden SVG block, then reference them anywhere on the page:
<!-- Icon definitions (hidden, place early in <body>) -->
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" aria-hidden="true">
<symbol id="icon-chevron" viewBox="0 0 24 24">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2"
stroke-linecap="round" fill="none"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24">
<path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2"
stroke-linecap="round" fill="none"/>
</symbol>
</svg>
<!-- Usage — reference by ID anywhere -->
<button class="btn">
<svg width="20" height="20" aria-hidden="true">
<use href="#icon-check"/>
</svg>
Confirm
</button>
<a href="/next" class="link">
Continue
<svg width="20" height="20" aria-hidden="true">
<use href="#icon-chevron"/>
</svg>
</a>
/* currentColor inherits from the parent element's color property */
.btn svg, .link svg {
fill: none;
stroke: currentColor;
vertical-align: middle;
}
.btn { color: #4f46e5; }
.link { color: #06b6d4; }
This sprite approach cuts HTTP requests significantly for icon-heavy UIs, and currentColor means your icons automatically match the surrounding text color — great for theming and dark mode.
SVG Animation with CSS
CSS-based svg animation is the most maintainable approach for most use cases. You can animate any presentation attribute that maps to a CSS property: fill, stroke, opacity, transform, and others.
Here's a practical loading spinner built entirely with CSS animation:
<svg class="spinner" viewBox="0 0 50 50" width="50" height="50"
role="status" aria-label="Loading">
<title>Loading</title>
<!-- Background track -->
<circle class="track" cx="25" cy="25" r="20"
fill="none" stroke="#e5e7eb" stroke-width="4"/>
<!-- Animated arc -->
<circle class="arc" cx="25" cy="25" r="20"
fill="none" stroke="#4f46e5" stroke-width="4"
stroke-linecap="round"/>
</svg>
.spinner {
animation: spin 1s linear infinite;
}
.arc {
/* circumference of circle: 2 * π * 20 ≈ 125.66 */
stroke-dasharray: 80 45.66;
stroke-dashoffset: 0;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes dash {
0% { stroke-dashoffset: 125; }
50% { stroke-dashoffset: 20; }
100% { stroke-dashoffset: 125; }
}
/* Respect reduced-motion preferences */
@media (prefers-reduced-motion: reduce) {
.spinner { animation: none; }
.arc { stroke-dasharray: 125; stroke-dashoffset: 0; }
}
The key technique here is stroke-dasharray and stroke-dashoffset. By setting stroke-dasharray to the circumference of the circle, you can control exactly how much of the stroke is visible — and animating stroke-dashoffset creates the illusion of a drawing or loading motion.
Path Drawing Animation
This same principle creates the popular "draw-on" effect for logos and illustrations:
.animated-path {
stroke-dasharray: 300; /* Must match or exceed actual path length */
stroke-dashoffset: 300;
animation: draw 2s ease forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
To find the exact path length for stroke-dasharray, use JavaScript: path.getTotalLength().
Accessibility for SVG
Decorative SVGs — icons that repeat information already in adjacent text — should be hidden from screen readers:
<button>
<svg width="16" height="16" aria-hidden="true" focusable="false">
<use href="#icon-download"/>
</svg>
Download report
</button>
Meaningful SVGs that convey information not available elsewhere need accessible names:
<!-- Standalone informational graphic -->
<svg viewBox="0 0 200 150" width="200" height="150"
role="img" aria-labelledby="chart-title chart-desc">
<title id="chart-title">Monthly revenue Q1 2025</title>
<desc id="chart-desc">Bar chart showing $45k in January, $52k in February, $61k in March</desc>
<!-- chart elements -->
</svg>
Note focusable="false" on SVGs inside buttons — IE11 would otherwise make them independently focusable, breaking keyboard navigation.
Optimizing SVG for Production
Editors like Illustrator and Figma export SVGs with a lot of unnecessary metadata, editor-specific attributes, and redundant decimal precision. A tool like SVGO typically reduces file size by 50–70%.
Integrate SVGO into your Vite build:
// vite.config.js
import { defineConfig } from 'vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default defineConfig({
plugins: [
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[name]',
svgoOptions: {
multipass: true,
plugins: [
{ name: 'preset-default', params: {
overrides: {
removeViewBox: false, // Always keep viewBox!
}
}},
{ name: 'removeMetadata' },
{ name: 'convertColors', params: { currentColor: true } },
]
}
})
]
})
Critical optimization rules:
- Never remove
viewBox— SVGO's default preset does this; always override it - Set
floatPrecision: 2to reduce123.456789to123.46across all coordinates - Use
convertColors: { currentColor: true }to replace hardcoded hex values withcurrentColorfor themeable icons - Remove
<title>, editor metadata, and comments from production builds
For SVGs you use as data URIs in CSS (useful for custom checkboxes, bullets, etc.), our SVG to CSS converter handles the encoding automatically.
Lazy Loading Below-Fold SVGs
For large, below-fold SVGs like hero illustrations, use an Intersection Observer to defer loading:
<div class="svg-placeholder"
data-svg-src="/illustrations/hero.svg"
style="width: 600px; height: 400px;">
</div>
const loadSVG = async (container) => {
const src = container.dataset.svgSrc
if (!src) return
try {
const response = await fetch(src)
const svgText = await response.text()
// Parse and inject safely
const parser = new DOMParser()
const doc = parser.parseFromString(svgText, 'image/svg+xml')
const svg = doc.querySelector('svg')
if (svg) {
// Preserve container dimensions
svg.setAttribute('width', container.offsetWidth)
svg.setAttribute('height', container.offsetHeight)
container.appendChild(svg)
}
} catch (err) {
console.warn('Failed to load SVG:', src, err)
}
}
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadSVG(entry.target)
obs.unobserve(entry.target)
}
})
}, { rootMargin: '200px' }) // Start loading 200px before entering viewport
document.querySelectorAll('[data-svg-src]').forEach(el => observer.observe(el))
The rootMargin: '200px' gives the fetch time to complete before the element actually scrolls into view, preventing a flash of missing content.
Interactive SVG with JavaScript
SVG elements are full DOM nodes, so you can attach event listeners, read and modify attributes, and integrate with your existing JavaScript framework. This example builds an interactive floor plan where rooms highlight on hover and show details on click:
<svg id="floorplan" viewBox="0 0 400 300" width="400" height="300"
role="img" aria-label="Office floor plan — click a room for details">
<title>Office floor plan</title>
<path class="room" id="room-a"
d="M20 20 H180 V140 H20 Z"
data-name="Conference Room A"
data-capacity="12"
data-available="true"/>
<path class="room" id="room-b"
d="M200 20 H380 V140 H200 Z"
data-name="Open Workspace"
data-capacity="24"
data-available="false"/>
<path class="room" id="room-c"
d="M20 160 H380 V280 H20 Z"
data-name="Cafeteria"
data-capacity="80"
data-available="true"/>
</svg>
<div id="room-info" hidden>
<h3 id="info-name"></h3>
<p>Capacity: <span id="info-capacity"></span></p>
<p>Status: <span id="info-status"></span></p>
</div>
.room {
fill: #f3f4f6;
stroke: #9ca3af;
stroke-width: 2;
cursor: pointer;
transition: fill 0.15s ease;
}
.room:hover,
.room:focus {
fill: #dbeafe;
outline: none;
}
.room[data-available="false"] {
fill: #fee2e2;
}
.room[data-available="false"]:hover {
fill: #fecaca;
}
.room.selected {
fill: #bfdbfe;
stroke: #3b82f6;
stroke-width: 3;
}
const infoPanel = document.getElementById('room-info')
let selectedRoom = null
document.querySelectorAll('.room').
Related Tools
SVG to CSS
Convert SVG files to CSS background images with proper encoding.
Favicon Generator
Create favicon packages with all sizes for browsers, iOS, and Android.
Base64 Encoder
Encode images and files to Base64 for embedding in CSS or HTML.
Animation Generator
Create CSS keyframe animations with visual timeline editor.
Continue Learning
CSS Animations and Transitions
Create smooth animations and transitions with pure CSS.
intermediateWeb Performance Optimization
Learn to build fast-loading websites with modern performance techniques.
intermediateHTML Performance Optimization: Speed Up Your Website in 2026
Learn how to optimize HTML for faster page loads. Cover lazy loading, preloading, resource hints, and Core Web Vitals optimization.