SVG for Web Developers: The Complete Guide to Scalable Vector Graphics
Everything you need to know about SVG — from coordinate systems and basic shapes to path commands, animations, icon sprite systems, optimization techniques, React integration, and accessibility best practices.
Table of Contents
Introduction — Why SVG Is Everywhere
Scalable Vector Graphics have quietly become one of the most important technologies on the modern web. If you open any well-built website today and inspect the source, you will find SVG everywhere: logos, icons, illustrations, charts, loading spinners, animated hero sections, and interactive data visualizations. Unlike raster formats such as PNG and JPEG that store images as grids of colored pixels, SVG describes graphics using mathematical instructions — points, lines, curves, and shapes defined in XML. This fundamental difference means that an SVG image looks perfectly sharp at any size, whether it is rendered on a 4K retina display or a small mobile screen. There is no pixelation, no blurriness, and no need to ship multiple resolution variants of the same asset.
The practical impact of this for web developers is enormous. A single SVG icon file can replace an entire set of @2x and @3x PNG sprites. A 2 KB SVG logo will render crisply at billboard scale while a comparable PNG might weigh 50 KB and still look blurry when scaled beyond its native resolution. SVG is also text-based XML, which means it can be inlined directly into your HTML, styled with CSS, manipulated with JavaScript, animated with CSS transitions or SMIL, and indexed by search engines. It compresses exceptionally well with gzip because it is plain text. And because SVG elements are part of the DOM when inlined, you can attach event listeners to individual shapes, change colors dynamically based on application state, and create interactive graphics that respond to user input without any canvas or WebGL overhead.
Despite these advantages, many developers treat SVG as a black box. They copy-paste SVG code from design tools, drop it into their projects, and never look inside. This works until it does not — until you need to change an icon color with a CSS custom property, until you need to animate a path drawing effect, until you need to build a reusable icon system that does not duplicate markup, or until your SVG assets are bloating your bundle size because the design tool exported them with unnecessary metadata. This guide will give you the deep understanding needed to work with SVG confidently and effectively, from the XML structure and coordinate system fundamentals through advanced topics like sprite systems, optimization pipelines, React component patterns, and accessibility requirements.
SVG Basics
XML Structure
Every SVG document is a well-formed XML document. The root element is <svg>, and it requires the SVG namespace declaration. When you inline SVG into HTML5 documents, the browser infers the namespace automatically, but when SVG files are served standalone (as .svg files), the namespace is required. Understanding this distinction matters because namespace issues are one of the most common reasons SVG fails to render when dynamically generated or manipulated with JavaScript.
<!-- Standalone SVG file -->
<svg xmlns="http://www.w3.org/2000/svg"
width="200" height="200"
viewBox="0 0 200 200">
<circle cx="100" cy="100" r="80" fill="#3b82f6" />
</svg>
<!-- Inlined in HTML5 (namespace optional but recommended) -->
<svg width="200" height="200" viewBox="0 0 200 200">
<circle cx="100" cy="100" r="80" fill="#3b82f6" />
</svg>
The viewBox Attribute
The viewBox attribute is arguably the single most important concept in SVG. It defines the internal coordinate system of the SVG canvas and is what enables SVG to be truly scalable. The syntax is viewBox="min-x min-y width height". The first two values set the origin point of the coordinate system, and the last two define the size of the visible area in SVG user units. When the viewBox dimensions differ from the width and height attributes on the <svg> element, the browser scales the content to fit, similar to how background-size: contain works in CSS. This is the mechanism that makes SVG resolution-independent: the viewBox defines the abstract coordinate space, and the width/height attributes (or CSS sizing) determine how large it renders on screen.
<!-- viewBox="0 0 100 100" means the internal canvas is 100x100 units -->
<!-- width="400" height="400" means it renders at 400x400 pixels -->
<!-- Everything inside is scaled up by 4x automatically -->
<svg width="400" height="400" viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80" fill="#ef4444" />
</svg>
<!-- Remove width/height to make SVG fully responsive -->
<!-- It will fill its container and scale proportionally -->
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80" fill="#ef4444" />
</svg>
Coordinate System and Units
SVG uses a coordinate system where the origin (0, 0) is at the top-left corner. The x-axis increases to the right, and the y-axis increases downward — the same convention as the browser's layout coordinate system but the opposite of the mathematical convention where y increases upward. All coordinates within the viewBox are expressed in abstract "user units" that have no inherent physical size. You can also use explicit units like px, em, %, cm, mm, in, pt, and pc on the width and height attributes, but inside the viewBox, everything is unitless. The preserveAspectRatio attribute controls how the viewBox content is scaled and aligned when the aspect ratio of the viewBox does not match the aspect ratio of the viewport. The default value xMidYMid meet centers the content and scales uniformly to fit entirely within the viewport, equivalent to CSS object-fit: contain.
<!-- preserveAspectRatio controls scaling behavior -->
<svg width="300" height="150" viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid meet">
<!-- Content is centered and scaled to fit (default) -->
</svg>
<svg width="300" height="150" viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid slice">
<!-- Content is centered and scaled to cover (like background-size: cover) -->
</svg>
<svg width="300" height="150" viewBox="0 0 100 100"
preserveAspectRatio="none">
<!-- Content is stretched to fill (distorts aspect ratio) -->
</svg>
viewBox and omit explicit width and height attributes. Then size the SVG with CSS. This gives you maximum flexibility — the same SVG can be rendered at 16px for inline text, 24px for toolbar icons, or 64px for feature cards, all from a single file.
Basic Shapes
SVG provides six primitive shape elements that cover the most common geometric forms. Each shape is defined by a specific set of attributes that control its position, dimensions, and geometry. Understanding these primitives is essential because even complex illustrations are ultimately composed of these building blocks combined with the path element. Design tools like Figma and Illustrator export simple shapes as these dedicated elements rather than generic paths, which produces cleaner, more readable SVG code that is easier to style and animate.
Rectangle — <rect>
<!-- Basic rectangle -->
<rect x="10" y="10" width="180" height="100" fill="#3b82f6" />
<!-- Rounded corners with rx and ry -->
<rect x="10" y="10" width="180" height="100" rx="15" ry="15"
fill="#3b82f6" stroke="#1d4ed8" stroke-width="2" />
<!-- If only rx is specified, ry defaults to the same value -->
<rect x="10" y="10" width="180" height="100" rx="15"
fill="none" stroke="#3b82f6" stroke-width="3" />
Circle — <circle>
<!-- cx, cy = center coordinates; r = radius -->
<circle cx="100" cy="100" r="80" fill="#10b981" />
<!-- Outlined circle -->
<circle cx="100" cy="100" r="80"
fill="none" stroke="#10b981" stroke-width="4" />
Ellipse — <ellipse>
<!-- rx = horizontal radius; ry = vertical radius -->
<ellipse cx="100" cy="75" rx="90" ry="60" fill="#f59e0b" />
Line — <line>
<!-- x1,y1 = start point; x2,y2 = end point -->
<line x1="10" y1="10" x2="190" y2="190"
stroke="#ef4444" stroke-width="3" stroke-linecap="round" />
Polyline — <polyline>
<!-- points = list of x,y coordinate pairs -->
<!-- Polyline is an open shape (not automatically closed) -->
<polyline points="10,190 50,10 90,120 130,30 170,150 190,50"
fill="none" stroke="#8b5cf6" stroke-width="3"
stroke-linejoin="round" stroke-linecap="round" />
Polygon — <polygon>
<!-- Same as polyline but automatically closes the shape -->
<!-- Triangle -->
<polygon points="100,10 190,190 10,190" fill="#06b6d4" />
<!-- Hexagon -->
<polygon points="50,0 100,25 100,75 50,100 0,75 0,25"
fill="#ec4899" stroke="#be185d" stroke-width="2" />
<polyline> and <polygon> is that polygon automatically connects the last point back to the first point, creating a closed shape. Polyline leaves the shape open. Both accept the same points attribute format.
Paths — The Most Powerful Element
The <path> element is the Swiss Army knife of SVG. Every shape you can draw with the basic shape elements can also be drawn with a path, plus infinitely more complex forms. Paths are defined by the d attribute (short for "data"), which contains a sequence of commands that tell the rendering engine where to move the pen and how to draw. When design tools export complex illustrations, almost everything becomes a path. When you open an SVG file and see a long string of letters and numbers in a d attribute, those are path commands. Understanding them is what separates developers who can modify and create SVG from those who can only copy-paste it. Each command is represented by a single letter followed by its parameters. Uppercase letters use absolute coordinates (relative to the SVG origin), while lowercase letters use relative coordinates (relative to the current pen position).
Path Command Reference
| Command | Name | Parameters | Description |
|---|---|---|---|
M / m | Move To | x y | Move the pen to a new position without drawing. Every path must start with M. |
L / l | Line To | x y | Draw a straight line from the current position to the specified point. |
H / h | Horizontal Line | x | Draw a horizontal line to the specified x-coordinate (y stays the same). |
V / v | Vertical Line | y | Draw a vertical line to the specified y-coordinate (x stays the same). |
C / c | Cubic Bezier | x1 y1 x2 y2 x y | Draw a cubic Bezier curve with two control points (x1,y1 and x2,y2) to endpoint (x,y). |
S / s | Smooth Cubic | x2 y2 x y | Shorthand cubic Bezier. The first control point is reflected from the previous curve's second control point. |
Q / q | Quadratic Bezier | x1 y1 x y | Draw a quadratic Bezier curve with one control point (x1,y1) to endpoint (x,y). |
T / t | Smooth Quadratic | x y | Shorthand quadratic Bezier. The control point is reflected from the previous curve. |
A / a | Elliptical Arc | rx ry rotation large-arc sweep x y | Draw an elliptical arc. The most complex command with 7 parameters. |
Z / z | Close Path | none | Close the path by drawing a straight line back to the starting point. |
Path Examples
<!-- Simple triangle using line commands -->
<path d="M 10 190 L 100 10 L 190 190 Z"
fill="#3b82f6" stroke="#1d4ed8" stroke-width="2" />
<!-- Same triangle using relative commands -->
<path d="M 10 190 l 90 -180 l 90 180 z"
fill="#3b82f6" />
<!-- Smooth wave using cubic Bezier curves -->
<path d="M 0 50 C 25 0, 75 0, 100 50 S 175 100, 200 50"
fill="none" stroke="#10b981" stroke-width="3" />
<!-- Heart shape using cubic Bezier curves -->
<path d="M 100 180
C 40 120, 0 80, 0 50
C 0 20, 20 0, 50 0
C 70 0, 90 15, 100 40
C 110 15, 130 0, 150 0
C 180 0, 200 20, 200 50
C 200 80, 160 120, 100 180 Z"
fill="#ef4444" />
<!-- Elliptical arc: semicircle -->
<path d="M 10 100 A 90 90 0 0 1 190 100"
fill="none" stroke="#f59e0b" stroke-width="3" />
<!-- Using H and V for a stepped path -->
<path d="M 10 190 V 130 H 70 V 90 H 130 V 50 H 190"
fill="none" stroke="#8b5cf6" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round" />
A) is notoriously confusing because of its large-arc-flag and sweep-flag parameters. The large-arc-flag (0 or 1) selects whether to draw the smaller or larger arc. The sweep-flag (0 or 1) selects the clockwise or counter-clockwise direction. When in doubt, try all four combinations of these two flags to see which one produces the arc you need.
Text in SVG
SVG has its own text rendering system that is independent from HTML text. The <text> element renders characters as vector outlines that scale perfectly at any size, which makes SVG text ideal for logos, labels on charts and diagrams, and any situation where text needs to be part of a scalable graphic. However, SVG text does not support automatic line wrapping, hyphenation, or the rich text layout capabilities that HTML and CSS provide. For paragraphs and flowing body text, HTML is always the better choice. Use SVG text for short labels, titles, and decorative typographic elements.
The text Element
<!-- Basic text positioned at x, y -->
<text x="10" y="50" font-family="sans-serif" font-size="24" fill="#e2e8f0">
Hello SVG
</text>
<!-- Text with anchor point (text-anchor controls horizontal alignment) -->
<text x="100" y="50" text-anchor="middle" font-size="20" fill="#3b82f6">
Centered Text
</text>
<!-- dominant-baseline controls vertical alignment -->
<text x="100" y="100" text-anchor="middle" dominant-baseline="middle"
font-size="18" fill="#10b981">
Vertically Centered
</text>
tspan for Inline Formatting
<!-- tspan allows styling portions of text differently -->
<text x="10" y="50" font-family="sans-serif" font-size="20" fill="#e2e8f0">
This is
<tspan fill="#ef4444" font-weight="bold">important</tspan>
text with
<tspan dy="1.2em" x="10">a new line using dy and x reset</tspan>
</text>
textPath for Curved Text
<!-- Text that follows a curved path -->
<svg viewBox="0 0 300 200">
<defs>
<path id="curve" d="M 20 150 Q 150 0 280 150" fill="none" />
</defs>
<text font-size="18" fill="#f59e0b">
<textPath href="#curve">
Text flowing along a curved path
</textPath>
</text>
</svg>
Styling SVG
SVG elements can be styled through three different mechanisms: presentation attributes (inline XML attributes), inline CSS (the style attribute), and external or embedded CSS stylesheets. Understanding the specificity and cascade order of these methods is critical for maintaining complex SVG graphics. Presentation attributes have the lowest specificity — they are equivalent to user-agent stylesheet declarations. This means any CSS rule, even one with the lowest specificity, will override a presentation attribute. This behavior is actually very useful because it allows you to define default styles as attributes in the SVG markup and override them with CSS for different themes, states, or contexts. Inline style attributes have the highest specificity, just like in HTML, and should be used sparingly.
Fill, Stroke, and Opacity
<!-- Presentation attributes (lowest specificity, easy to override) -->
<rect width="100" height="100" fill="#3b82f6" stroke="#1d4ed8"
stroke-width="3" opacity="0.8" />
<!-- fill-opacity and stroke-opacity for independent transparency -->
<circle cx="100" cy="100" r="50"
fill="#ef4444" fill-opacity="0.5"
stroke="#ef4444" stroke-opacity="1" stroke-width="3" />
<!-- Stroke properties -->
<line x1="10" y1="10" x2="190" y2="10"
stroke="#e2e8f0" stroke-width="4"
stroke-dasharray="10 5"
stroke-linecap="round" />
CSS Styling
/* External CSS can target SVG elements */
svg rect {
fill: #3b82f6;
stroke: #1d4ed8;
stroke-width: 2;
transition: fill 0.2s ease;
}
svg rect:hover {
fill: #60a5fa;
}
/* Use CSS custom properties for theming */
:root {
--icon-color: #3b82f6;
--icon-size: 24px;
}
.icon {
width: var(--icon-size);
height: var(--icon-size);
fill: var(--icon-color);
}
/* Dark mode override */
@media (prefers-color-scheme: dark) {
:root {
--icon-color: #93c5fd;
}
}
Gradients
<svg viewBox="0 0 200 200">
<defs>
<!-- Linear gradient -->
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#3b82f6" />
<stop offset="100%" stop-color="#8b5cf6" />
</linearGradient>
<!-- Radial gradient -->
<radialGradient id="grad2" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="100%" stop-color="#ef4444" />
</radialGradient>
</defs>
<rect width="200" height="100" fill="url(#grad1)" />
<circle cx="100" cy="150" r="45" fill="url(#grad2)" />
</svg>
Patterns
<svg viewBox="0 0 200 200">
<defs>
<pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="3" fill="#3b82f6" opacity="0.3" />
</pattern>
</defs>
<rect width="200" height="200" fill="url(#dots)" />
</svg>
Transforms and Animations
SVG supports geometric transformations that let you translate, rotate, scale, and skew elements without changing their underlying geometry. These transforms can be applied as XML attributes or through CSS, and they form the basis for creating animations and dynamic effects. The transform origin in SVG behaves differently from HTML by default: in SVG, the transform origin is the origin of the SVG coordinate system (0, 0), not the center of the element. This catches many developers off guard when rotating or scaling SVG elements. You can override this with the CSS transform-origin property or by combining a translate before and after the rotation to effectively change the pivot point.
Transform Attribute
<!-- Translate: move 50 units right and 30 units down -->
<rect width="40" height="40" fill="#3b82f6"
transform="translate(50, 30)" />
<!-- Rotate: 45 degrees around point (100, 100) -->
<rect x="80" y="80" width="40" height="40" fill="#ef4444"
transform="rotate(45, 100, 100)" />
<!-- Scale: 2x horizontal, 1.5x vertical -->
<circle cx="50" cy="50" r="20" fill="#10b981"
transform="scale(2, 1.5)" />
<!-- Chain multiple transforms (applied right to left) -->
<rect width="40" height="40" fill="#f59e0b"
transform="translate(100, 100) rotate(45) scale(1.5)" />
CSS Animations
/* Spinning loader icon */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
transform-origin: center;
animation: spin 1s linear infinite;
}
/* Dash drawing effect (stroke animation) */
@keyframes dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
.draw-path {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 2s ease-in-out forwards;
}
/* Pulsing circle */
@keyframes pulse {
0%, 100% { r: 20; opacity: 1; }
50% { r: 30; opacity: 0.6; }
}
.pulse-circle {
animation: pulse 2s ease-in-out infinite;
}
SMIL Animations
SMIL (Synchronized Multimedia Integration Language) animations are declared directly in SVG markup and do not require CSS or JavaScript. While SMIL was once deprecated by Chrome, that decision was reversed, and SMIL is now supported in all modern browsers. SMIL is particularly useful for animations that are self-contained within SVG files, such as animated icons or loading spinners that need to work even when served as external image files.
<!-- Color transition animation -->
<circle cx="100" cy="100" r="40" fill="#3b82f6">
<animate attributeName="fill"
values="#3b82f6;#ef4444;#10b981;#3b82f6"
dur="3s" repeatCount="indefinite" />
</circle>
<!-- Motion along a path -->
<circle r="8" fill="#f59e0b">
<animateMotion dur="4s" repeatCount="indefinite"
path="M 20 100 Q 100 20 180 100 Q 100 180 20 100" />
</circle>
<!-- Transform animation -->
<rect x="-20" y="-20" width="40" height="40" fill="#8b5cf6">
<animateTransform attributeName="transform" type="rotate"
from="0 100 100" to="360 100 100"
dur="3s" repeatCount="indefinite" />
</rect>
prefers-reduced-motion, and can be controlled with JavaScript via the Web Animations API. Reserve SMIL for standalone SVG files that need to animate independently of the page's CSS.
SVG Sprites and Icon Systems
If your application uses more than a handful of SVG icons, you need a system for managing them. The most effective approach is the SVG sprite technique using <symbol> and <use> elements. This pattern lets you define each icon once in a hidden SVG block and reference it throughout your HTML by ID, eliminating duplicate markup while keeping the icons as part of the DOM (unlike image sprites, which are opaque to CSS). A well-built SVG sprite system can contain hundreds of icons in a single file that is downloaded once, cached by the browser, and reused across every page of your application. The <symbol> element is preferred over <defs> for icon definitions because symbol supports its own viewBox attribute, allowing each icon to have its own independent coordinate system.
Creating an Icon Sprite Sheet
<!-- Hidden SVG sprite sheet (place at the top of the body or in a template) -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<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"
stroke-linecap="round" />
</symbol>
<symbol id="icon-menu" viewBox="0 0 24 24">
<line x1="3" y1="6" x2="21" y2="6"
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<line x1="3" y1="12" x2="21" y2="12"
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<line x1="3" y1="18" x2="21" y2="18"
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<line x1="6" y1="6" x2="18" y2="18"
stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</symbol>
</svg>
<!-- Usage: reference icons with the <use> element -->
<svg class="icon" width="24" height="24">
<use href="#icon-search" />
</svg>
<svg class="icon" width="32" height="32">
<use href="#icon-menu" />
</svg>
<button>
<svg class="icon" width="20" height="20">
<use href="#icon-close" />
</svg>
Close
</button>
/* Style all icons consistently */
.icon {
fill: currentColor; /* Inherits color from parent text */
stroke: currentColor;
vertical-align: middle;
flex-shrink: 0;
}
/* Icons inherit the parent's text color */
.nav-link {
color: #94a3b8;
}
.nav-link:hover {
color: #e2e8f0;
}
/* The icon inside .nav-link automatically changes color */
currentColor as the fill or stroke value is the key technique that makes SVG icons work seamlessly with your CSS color system. It tells the SVG to use the computed color property of the nearest ancestor, so icon colors automatically match the surrounding text and respond to hover states, theme changes, and focus styles without any extra CSS.
SVG in HTML
There are four primary ways to include SVG in an HTML document, and each method comes with different tradeoffs regarding styling control, caching behavior, performance, and interactivity. Choosing the right embedding method is an architectural decision that affects your entire project, so it is worth understanding the implications of each approach before committing to one. The right choice depends on whether the SVG needs to be interactive, whether it needs to inherit styles from the page, whether caching as a separate asset matters, and how many SVG elements exist on the page.
| Method | CSS Styling | JS Access | Caching | HTTP Request | Best For |
|---|---|---|---|---|---|
| Inline SVG | Full control | Full DOM access | No (part of HTML) | None | Icons, interactive graphics, animations |
<img> tag |
None (sandboxed) | None | Yes (browser cache) | 1 per image | Static illustrations, logos, decorative images |
| CSS background | None (sandboxed) | None | Yes (browser cache) | 1 per image | Decorative backgrounds, patterns, textures |
<object> tag |
Via SVG's own CSS | Via contentDocument | Yes (browser cache) | 1 per object | Complex interactive SVGs, third-party SVGs |
<!-- 1. Inline SVG -->
<svg width="24" height="24" viewBox="0 0 24 24" class="icon">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"
fill="none" stroke="currentColor" stroke-width="2" />
</svg>
<!-- 2. img tag -->
<img src="/images/logo.svg" alt="Company Logo" width="200" height="60" />
<!-- 3. CSS background -->
<div class="hero" style="background-image: url('/images/pattern.svg');"></div>
<!-- 4. object tag -->
<object type="image/svg+xml" data="/images/interactive-chart.svg"
width="600" height="400">
<img src="/images/chart-fallback.png" alt="Chart" />
</object>
<img> for content images where alt text is important and caching matters. Use CSS backgrounds only for decorative elements. The <object> tag is rarely needed in modern development but remains useful when you need to embed a self-contained SVG application with its own scripts and styles.
SVG Optimization
SVG files exported from design tools like Figma, Sketch, Illustrator, and Inkscape almost always contain unnecessary metadata, comments, hidden layers, editor-specific attributes, default values that match the SVG spec, and excessive decimal precision in coordinate values. A typical Illustrator export might be three to five times larger than necessary. Optimizing SVG reduces file size, improves rendering performance, and produces cleaner markup that is easier to maintain and style with CSS. Optimization should be a standard part of your asset pipeline, just like image compression for raster graphics.
SVGO
SVGO (SVG Optimizer) is the industry-standard tool for SVG optimization. It runs a series of configurable plugins that clean up SVG code. SVGO is available as a Node.js CLI tool, a library, and as the engine behind the popular web-based tool SVGOMG. Most build tools and bundlers have SVGO integrations, so you can automatically optimize SVG assets as part of your build process.
# Install SVGO
npm install -g svgo
# Optimize a single file
svgo input.svg -o output.svg
# Optimize all SVGs in a directory
svgo -f ./src/assets/icons/ -o ./dist/icons/
# Show optimization stats
svgo input.svg -o output.svg --pretty --indent=2
# Use a custom config file
svgo --config svgo.config.js input.svg
// svgo.config.js - recommended configuration
module.exports = {
plugins: [
'preset-default', // All default optimizations
'removeDimensions', // Remove width/height, keep viewBox
'sortAttrs', // Sort attributes for consistency
{
name: 'removeAttrs',
params: {
attrs: ['data-name'] // Remove editor-specific attributes
}
},
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false, // NEVER remove viewBox
cleanupIds: false // Preserve IDs if using sprites
}
}
}
]
};
Manual Cleanup Techniques
- Remove metadata and comments: Elements like
<metadata>,<!-- comments -->, and<desc>(unless needed for accessibility) add bytes without visual impact. - Reduce decimal precision: Coordinates like
12.345678can usually be rounded to12.35or even12.3without visible difference. At icon sizes, even whole numbers often suffice. - Remove default attribute values: Attributes like
fill-rule="nonzero"andstroke-miterlimit="4"are SVG defaults and can be safely removed. - Convert shapes to paths: Sometimes converting basic shapes like
<rect>and<circle>to<path>produces shorter code, especially for simple shapes. - Simplify path data: Use relative commands where they produce shorter strings. Remove unnecessary spaces. Combine consecutive line segments.
- Use CSS instead of attributes: If multiple elements share the same fill or stroke, move those styles to a CSS class to avoid repetition.
<!-- BEFORE: Raw Illustrator export (680 bytes) -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" id="Layer_1" x="0px" y="0px"
width="24px" height="24px" viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;"
xml:space="preserve">
<!-- Generator: Adobe Illustrator 27.0 -->
<metadata>...</metadata>
<path fill-rule="nonzero" fill="#000000" d="M 12.000000,2.000000
L 2.000000,7.000000 L 12.000000,12.000000
L 22.000000,7.000000 L 12.000000,2.000000 Z"/>
</svg>
<!-- AFTER: Optimized (128 bytes) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5z"/>
</svg>
viewBox attribute. Some older SVGO configurations had removeViewBox enabled by default, which breaks SVG responsiveness. Always verify that removeViewBox is set to false in your SVGO configuration.
SVG in React
React and SVG are a natural fit. Because SVG is XML-based and React uses JSX (which is also XML-like), you can embed SVG directly in your React components with minimal modifications. However, there are several key differences between SVG-in-HTML and SVG-in-JSX that trip up developers, particularly around attribute naming. JSX requires camelCase for multi-word attributes, so stroke-width becomes strokeWidth, fill-opacity becomes fillOpacity, clip-path becomes clipPath, and xmlns:xlink becomes xmlnsXlink. The class attribute becomes className, and the for attribute (rare in SVG) becomes htmlFor. Additionally, self-closing tags are mandatory in JSX, so <path d="..."> must become <path d="..." />.
Basic SVG Component
// Icon component with size and color props
function SearchIcon({ size = 24, color = 'currentColor', ...props }) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
);
}
// Usage
<SearchIcon />
<SearchIcon size={32} color="#3b82f6" />
<SearchIcon size={16} className="nav-icon" />
Reusable Icon System
// Generic Icon wrapper component
function Icon({
children,
size = 24,
color = 'currentColor',
viewBox = '0 0 24 24',
className = '',
title,
...props
}) {
return (
<svg
width={size}
height={size}
viewBox={viewBox}
fill="none"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={`icon ${className}`}
role={title ? 'img' : 'presentation'}
aria-hidden={title ? undefined : true}
{...props}
>
{title && <title>{title}</title>}
{children}
</svg>
);
}
// Individual icon components are tiny
function HomeIcon(props) {
return (
<Icon {...props}>
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</Icon>
);
}
function SettingsIcon(props) {
return (
<Icon {...props}>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0
010-2.83l-.06-.06a1.65 1.65 0 00-1.82-.33" />
</Icon>
);
}
Dynamic SVG with Props
// Progress ring component
function ProgressRing({ progress = 0, size = 120, strokeWidth = 8 }) {
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (progress / 100) * circumference;
return (
<svg width={size} height={size}>
{/* Background circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#1e293b"
strokeWidth={strokeWidth}
/>
{/* Progress circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#3b82f6"
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
transform={`rotate(-90 ${size / 2} ${size / 2})`}
style={{ transition: 'stroke-dashoffset 0.3s ease' }}
/>
{/* Percentage text */}
<text
x="50%"
y="50%"
textAnchor="middle"
dominantBaseline="middle"
fontSize="24"
fill="#e2e8f0"
>
{Math.round(progress)}%
</text>
</svg>
);
}
// Usage
<ProgressRing progress={75} />
Convert SVG files to React components instantly with our free SVG to React Converter.
Open SVG to React ConverterAccessibility
SVG accessibility is frequently overlooked, but it is just as important as making HTML content accessible. Screen readers and assistive technologies need explicit hints to understand SVG content because, unlike HTML, SVG elements do not have inherent semantic meaning. A <circle> element has no accessible role by default — it is just a rendered shape. Without proper accessibility markup, all of your carefully crafted SVG icons and graphics will be invisible or confusing to users who rely on assistive technology. The good news is that making SVG accessible requires just a few attributes and elements, and the patterns are straightforward once you learn them. The approach you take depends on whether the SVG is decorative (adds no meaningful information) or informative (conveys content that would be lost if the graphic were removed).
Decorative SVG
Decorative SVGs are graphics that serve a purely visual or aesthetic purpose and do not convey information. Examples include background patterns, separator lines, and icons that sit next to text labels that already describe the action. These should be hidden from screen readers entirely.
<!-- Decorative: hide from assistive technology -->
<svg aria-hidden="true" focusable="false" width="24" height="24"
viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5z" fill="currentColor" />
</svg>
<!-- Icon next to a visible label (icon is decorative) -->
<button>
<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24">
<path d="M19 7l-7 7-7-7" stroke="currentColor" fill="none" />
</svg>
Download File
</button>
Informative SVG
Informative SVGs convey meaning that is not available through surrounding text. An icon-only button without a visible label is the most common example. For these, you must provide an accessible name using one or more of the following techniques.
<!-- Method 1: role="img" + aria-label -->
<svg role="img" aria-label="Search" width="24" height="24"
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" />
</svg>
<!-- Method 2: <title> element (provides tooltip in some browsers) -->
<svg role="img" aria-labelledby="search-title" width="24" height="24"
viewBox="0 0 24 24">
<title id="search-title">Search</title>
<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" />
</svg>
<!-- Method 3: <title> + <desc> for complex graphics -->
<svg role="img" aria-labelledby="chart-title chart-desc"
viewBox="0 0 400 300">
<title id="chart-title">Monthly Revenue</title>
<desc id="chart-desc">
Bar chart showing revenue growth from $10K in January
to $45K in June 2026.
</desc>
<!-- chart graphics here -->
</svg>
<!-- Icon-only button: the button provides the accessible name -->
<button aria-label="Close dialog">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18" stroke="currentColor"
stroke-width="2" />
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor"
stroke-width="2" />
</svg>
</button>
Accessibility Best Practices Summary
- Decorative icons: Use
aria-hidden="true"andfocusable="false"to remove them from the accessibility tree entirely. - Informative standalone SVGs: Add
role="img"and provide an accessible name viaaria-labeloraria-labelledbypointing to a<title>element. - Complex informative SVGs: Use both
<title>and<desc>elements, linked viaaria-labelledbywith space-separated IDs. - Icon-only buttons: Put
aria-labelon the<button>element and mark the SVG asaria-hidden="true". The button is the interactive element, so the accessible name belongs there. - Always test: Use a screen reader (VoiceOver on macOS, NVDA on Windows) to verify that your SVGs are announced correctly or properly hidden. Automated tools like axe-core can catch many common issues.
focusable="false" attribute is important for Internet Explorer and some older Edge versions, where SVG elements are focusable by default. While this is less of a concern with modern browsers, including it is a harmless safeguard that prevents unexpected tab-stop behavior on decorative SVGs.
Conclusion
SVG is one of those technologies that rewards deep understanding disproportionately. The developer who understands viewBox, coordinate systems, and path commands can build icon systems, create responsive illustrations, animate UI elements, optimize asset pipelines, and write accessible graphics. The developer who treats SVG as an opaque blob copied from Figma will hit walls repeatedly — when an icon refuses to change color, when an animation breaks because the transform origin is wrong, when the bundle size grows because every icon is duplicated, or when an accessibility audit flags a page full of unlabeled graphics.
The key takeaways from this guide are practical and immediate. First, always use viewBox and omit fixed width/height attributes to make your SVGs truly responsive. Second, learn the path command syntax — even a basic understanding of M, L, C, and Z will let you read, debug, and modify path data that would otherwise be incomprehensible. Third, build an icon system using <symbol> and <use> rather than inlining the same SVG markup everywhere. Fourth, optimize every SVG that enters your project with SVGO or manual cleanup. Fifth, use currentColor to make your SVG icons inherit colors from the surrounding CSS. Sixth, make every SVG accessible by either hiding it from screen readers (decorative) or providing an explicit accessible name (informative). And seventh, when working in React, create reusable icon components with size and color props that spread additional attributes for maximum flexibility.
SVG has been a web standard since 2001, and it has only become more relevant with the rise of responsive design, retina displays, component-based frameworks, and the growing emphasis on web performance and accessibility. It is not a niche format for designers — it is a core web technology that every frontend developer should be fluent in. The time you invest in understanding SVG will pay off every time you need to build a crisp, scalable, themeable, animated, accessible graphic for the web.