SVG Icon Embedding Methods Compared
| Method | Styleable | Cacheable | Accessible | Best For |
|---|---|---|---|---|
| Inline SVG | Full CSS/JS access | No (in HTML) | Yes (with aria) | Icons that need color theming |
<img src="icon.svg"> | No (opaque) | Yes (separate file) | Yes (with alt) | Static decorative icons |
| CSS background-image | No | Yes | No (background) | Decorative only, no screen readers |
SVG sprite + <use> | Full CSS access | Yes (sprite file) | Yes (with aria) | Icon systems, many repeated icons |
Inline SVG: Full Control
Inline SVG is embedded directly in the HTML. It can be styled with CSS, manipulated with JS, and animates with CSS transitions. Best for interactive, theme-aware icons.
<!-- Inline SVG — full CSS access -->
<button class="btn">
<svg xmlns="http://www.w3.org/2000/svg"
width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true" focusable="false">
<path d="M12 5v14M5 12l7 7 7-7"/>
</svg>
Download
</button>
<style>
/* currentColor inherits the text color — works with dark mode automatically */
.btn { color: #7c6fff; }
.btn:hover { color: #9d96ff; }
/* The SVG stroke changes color with the button text */
</style>
SVG Sprite: The Scalable System
An SVG sprite file defines all icons as <symbol> elements, then references them with <use>. The sprite file is cached by the browser; you get full CSS styling on each instance.
<!-- sprite.svg (placed anywhere accessible) -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-arrow-down" viewBox="0 0 24 24">
<path d="M12 5v14M5 12l7 7 7-7" fill="none" stroke="currentColor" stroke-width="2"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2"/>
</symbol>
<symbol id="icon-x" 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"/>
</symbol>
</svg>
<!-- Usage anywhere in HTML -->
<svg width="20" height="20" aria-hidden="true" focusable="false">
<use href="/sprite.svg#icon-arrow-down"/>
</svg>
<svg width="20" height="20" aria-hidden="true" focusable="false">
<use href="/sprite.svg#icon-check"/>
</svg>
SVG Accessibility: Decorative vs Informative
Icons are either decorative (redundant with adjacent text) or informative (convey information on their own). Each case needs different ARIA treatment.
<!-- Decorative icon (label is in the button text) -->
<button>
<svg aria-hidden="true" focusable="false" width="20" height="20">
<!-- aria-hidden removes from accessibility tree entirely -->
<use href="/sprite.svg#icon-download"/>
</svg>
Download PDF
</button>
<!-- Informative icon (no adjacent text label) -->
<button aria-label="Download PDF">
<svg role="img" aria-labelledby="download-title" width="20" height="20">
<title id="download-title">Download PDF</title>
<use href="/sprite.svg#icon-download"/>
</svg>
</button>
<!-- Icon-only button — simplest approach -->
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false" width="24" height="24">
<use href="/sprite.svg#icon-x"/>
</svg>
</button>
Dark Mode SVG Icons
/* Approach 1: currentColor (works automatically in both modes) */
svg { color: inherit; } /* or stroke: currentColor on paths */
/* Approach 2: CSS custom property for explicit control */
:root { --icon-color: #475569; }
[data-theme="dark"] { --icon-color: #94a3b8; }
svg path { stroke: var(--icon-color); }
/* Approach 3: Dark mode via prefers-color-scheme inside SVG file */
<!-- In icon.svg file: -->
<svg>
<style>
path { fill: #1e293b; }
@media (prefers-color-scheme: dark) {
path { fill: #e2e8f0; }
}
</style>
<path d="..."/>
</svg>
Common SVG Icon Mistakes
- Hardcoding fill/stroke colors — use
currentColorinstead so icons inherit text color and work with dark mode automatically. - Missing viewBox — without viewBox, SVG won't scale properly when you change the width/height. Always include viewBox on every SVG.
- No aria-hidden on decorative icons — screen readers will read out SVG path data as gibberish without
aria-hidden="true". - Serving icon fonts instead of SVG — icon fonts (Font Awesome) are a single CSS+font request, but SVG sprites are more performant, more accessible, and easier to customize. Prefer SVG.
- Not optimizing SVG source files — Illustrator/Figma export includes unnecessary metadata. Run through SVGO or StudioLimb's SVG Optimizer before deployment.
Frequently Asked Questions
- Should I use Material Icons, Heroicons, or custom SVGs?
- Icon libraries (Heroicons, Phosphor, Lucide) are excellent for product UIs — consistent style, already accessible, easy to use. Custom SVGs are better for brand-specific logos or illustrations. For most projects, an icon library + your logo as SVG is the right split.
- What's the ideal SVG icon size for web?
- Design icons on a 24×24 grid (the standard for most libraries). Render at 20px or 24px in the UI, with 16px for dense UIs and 32px+ for larger touch targets. Never render icons below 16px — details disappear and accessibility suffers.
- Can I animate SVG icons?
- Yes — CSS transitions and animations work on SVG properties. Transform, opacity, stroke-dasharray, and fill can all be animated. For complex icon animations, GSAP or CSS @keyframes on SVG elements are both good approaches.
- How do I optimize an SVG file?
- Run it through SVGO (command line) or StudioLimb's SVG Optimizer (browser). SVGO removes comments, unnecessary groups, default attributes, and whitespace — typically reducing file size by 20–60% with no visual change.