Icon fonts mapped pictographic glyphs to Unicode code points inside a font file. Point your CSS at the font, use a pseudo-element or special character, and the icon renders like text. FontAwesome, Material Icons, and Ionicons popularized this pattern around 2012-2015. It was genuinely clever: one HTTP request loaded hundreds of icons, they scaled with font-size, and you could color them with CSS color.
Then SVG caught up. Browser support for inline SVG hit 100%. HTTP/2 made multiple small requests cheap. JavaScript bundlers added tree shaking. And the accumulated downsides of icon fonts — accessibility problems, font loading flashes, alignment headaches, single-color limitation — became harder to ignore. This guide compares both approaches with concrete numbers and delivers the verdict.
Icon Fonts: How They Work
An icon font is a regular font file (TTF, WOFF2, etc.) where the glyphs are icons instead of letters. FontAwesome maps its icons to the Private Use Area (U+F000-U+F8FF) of Unicode — code points reserved for custom symbols that won't conflict with real text characters.
Implementation Pattern
/* Load the icon font */
@font-face {
font-family: 'FontAwesome';
src: url('/fonts/fa-solid-900.woff2') format('woff2');
font-display: block;
}
/* Apply via CSS pseudo-element */
.fa-home::before {
font-family: 'FontAwesome';
content: '\f015'; /* Unicode code point for home icon */
}
/* Or via HTML entity */
<i class="fa fa-home"></i>The icon renders as a text glyph, so it inherits color, font-size, text-shadow, and other text properties from CSS. This was the killer feature: style icons with the same CSS you use for text.
What Icon Fonts Got Right
- Single HTTP request: One WOFF2 file delivers hundreds of icons. In HTTP/1.1 (max 6 concurrent connections per domain), this mattered enormously
- CSS-only styling: Change icon color with
color, size withfont-size, add shadows withtext-shadow. No JavaScript needed - Scales perfectly: Vector glyphs look crisp at any size — no pixelation, no multiple resolutions
- Tiny per-icon cost: Each additional icon is just a few bytes of glyph data in the font file
What Icon Fonts Got Wrong
- Accessibility: Screen readers see the Unicode code point, not the icon's meaning.
\f015is announced as nothing or as a random character, depending on the screen reader. You need additionalaria-hidden="true"on the icon element and a separatearia-labelorsr-onlytext for the meaning. Many implementations skip this - Font loading flash: Before the icon font loads, users see blank rectangles or incorrect Unicode characters where icons should be.
font-display: blockhides them (invisible icons for up to 3 seconds).font-display: swapshows garbage characters. Neither is good - Alignment headaches: Icons are text glyphs with ascenders, descenders, and line-height. Vertically centering an icon next to text requires fiddly CSS —
vertical-align,line-heightadjustments, negative margins. Different icons sit at different baselines within the same font - Single color only: Font glyphs are monochromatic. Multi-color icons require stacking multiple glyphs (FontAwesome's "duotone" approach), which doubles the DOM elements and CSS complexity
- All-or-nothing loading: Even if you use 5 icons from a 1,000-icon font, the entire font downloads. FontAwesome's free WOFF2 is 130 KB. Their kit builder offers custom subsets, but most developers ship the full file
- Anti-aliasing inconsistency: Browsers apply font anti-aliasing (subpixel or grayscale) to icon font glyphs. This can make icons look blurry at certain sizes or on certain OS/browser combinations. SVG rendering doesn't have this issue
SVG Icons: How They Work
SVG (Scalable Vector Graphics) icons are XML-based vector images. Each icon is a self-contained <svg> element with <path> elements describing the icon shape. Three delivery methods exist, each with different trade-offs.
Inline SVG (Recommended)
<!-- Inline SVG — full CSS/JS control, accessible, no HTTP request -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"
role="img" aria-label="Home">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" fill="currentColor"/>
</svg>Inline SVG is part of the DOM. You can style it with CSS (fill, stroke, width), animate it with CSS or JS, and make it accessible with role="img" and aria-label. The icon renders immediately — no font loading, no HTTP request, no FOUT.
The trade-off: each inline SVG adds to your HTML size. A typical 24px icon is 100-300 bytes of SVG markup. On a page with 50 icons, that's 5-15 KB of added HTML — trivial compared to the 130 KB icon font it replaces.
SVG Sprite
<!-- Define sprites once (hidden) -->
<svg style="display: none;">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5z"/>
</symbol>
</svg>
<!-- Use via <use> reference -->
<svg width="24" height="24" role="img" aria-label="Home">
<use href="#icon-home"/>
</svg>The sprite approach defines all icons once and references them with <use>. This avoids duplicating SVG markup for icons used multiple times. The sprite can be inlined in the HTML or loaded as an external file. External sprites have a caching advantage but cross-origin limitations.
SVG in
Tag
<img src="/icons/home.svg" alt="Home" width="24" height="24">Using SVG as an image source is the simplest approach, but you lose CSS styling control (can't change fill color from CSS), JavaScript access, and the ability to animate individual paths. It's fine for decorative icons where you don't need to change color, but for UI icons that should match text color, inline SVG is better.
Head-to-Head Comparison
| Criterion | Icon Fonts | SVG (inline) | Winner |
|---|---|---|---|
| Accessibility | Poor — requires manual aria fixes | Native — role, aria-label, title | SVG |
| Font loading issues | FOUT shows garbage, FOIT hides icons | None — renders immediately | SVG |
| Multi-color | No (single color via CSS color) | Yes (multiple fills, gradients) | SVG |
| Tree shaking | No — full font always loads | Yes — bundle only used icons | SVG |
| CSS styling | color, font-size, text-shadow | fill, stroke, width, height, transforms | SVG |
| JS manipulation | Limited — pseudo-elements are tricky | Full DOM access to paths | SVG |
| Animation | Basic (color, size transitions) | Full (path morphing, drawing) | SVG |
| Alignment | Fragile — baseline/line-height issues | Precise — viewBox + width/height | SVG |
| Performance (50 icons) | 130 KB font + CSS | ~10 KB inline SVG markup | SVG |
| Performance (500 icons) | 130 KB font (amortized) | ~100 KB (or 20 KB sprite gzipped) | Tie |
| Caching | Font file cached across pages | Inline SVG not cached (per-page) | Icon font |
The Verdict
SVG wins on every criterion except caching of inline SVG (solved by sprites or component extraction). The accessibility gap alone should settle the debate — icon fonts are inherently inaccessible without manual ARIA intervention that most implementations skip.
Icon fonts were a brilliant HTTP/1.1-era optimization. HTTP/2 multiplexing, tree-shaking bundlers, and universal SVG support made that optimization unnecessary. If you're starting a new project, use SVG. If you're maintaining a project with icon fonts, plan a migration.
Migrating from Icon Fonts to SVG
The migration path depends on which icon library you're using:
FontAwesome
FontAwesome offers an SVG+JS version that replaces <i class="fa fa-home"></i> elements with inline SVG automatically. Switch from the webfont CSS to the SVG+JS script:
<!-- Remove this -->
<link rel="stylesheet" href="/css/fontawesome.css">
<!-- Add this -->
<script src="/js/fontawesome.min.js"></script>For better tree shaking, use FontAwesome's individual SVG icon packages with your bundler: import { faHome } from '@fortawesome/free-solid-svg-icons'. This ships only the icons you import, not the entire library.
Framework Icon Libraries
- React:
react-icons— imports individual SVG components from 20+ icon sets. Fully tree-shakeable - Vue:
unplugin-icons— auto-imports SVG icons as Vue components from Iconify's 100+ icon sets - Svelte:
svelte-iconsorunplugin-icons - Framework-agnostic: Iconify — unified API for 150,000+ icons across 100+ sets, with SVG output for any framework
All of these deliver inline SVG in the final HTML. You get the accessibility, styling, and performance benefits of SVG with the convenience of a <Icon name="home" /> component.
When Icon Fonts Still Make Sense
In rare cases, icon fonts remain practical:
- Email templates: Email clients strip SVG. Icon fonts (embedded as base64 data URIs or from a CDN) are the only way to get vector icons in email. Most email designers just use PNG images instead
- Legacy codebases: If your project has thousands of
<i class="fa">elements and no component system, the migration effort may not justify the improvement. FontAwesome's SVG+JS replacement script handles this without touching HTML - Material Symbols variable font: Google's Material Symbols uses a variable font with FILL, wght, GRAD, and opsz axes. This is genuinely better than SVG for Material icons because one 300 KB file delivers 3,000+ icons with customizable fill, weight, and grade — something SVG sprites can't match without duplicating files
The icon font era is over. SVG icons are more accessible, more performant (when tree-shaken), more styleable, and immune to font loading issues. The migration from icon fonts to SVG is straightforward — libraries like react-icons, Iconify, and FontAwesome's SVG+JS mode make it a drop-in replacement.
If you have icon font files you need to inspect or convert, you can convert WOFF2 to TTF to open them in FontForge or other font editors. For generating SVG versions of individual glyphs, export from the TTF using FontForge's glyph export feature.