How to Migrate from Icon Fonts to SVG Icons

This guide covers the most common legacy pattern in SVG & Icon Optimization, part of Image & Media Optimization. Icon fonts — Font Awesome's webfont build, Material Icons font, custom IcoMoon sets — were the standard way to ship icons a decade ago. They package hundreds of glyphs into one font file and a stylesheet that maps class names to private-use characters. They also bring the full cost of web fonts to every icon on the page.

The font file and its stylesheet are usually on the critical path. Until the font loads, icons are invisible (with font-display: block) or render as fallback characters and boxes (with swap), then pop in — sometimes changing width and shifting layout. Screen readers may announce private-use characters as gibberish. And the font contains every glyph in the set, even if a page uses six. SVG icons solve all of these, and the migration can be done incrementally.

Icon font vs SVG icons Comparison of the costs of an icon font and an SVG icon system. Icon font vs SVG icons Icon font • CSS + font file on the critical path • Icons invisible or wrong until font loads • Swap can shift layout • All glyphs shipped; a11y pitfalls SVG icons • Inline or sprite: no font request • Icons render with the HTML • Fixed dimensions: no shift • Only icons used; aria-hidden or labelled

Rapid Diagnosis

  • Find icon fonts in the Network panel. Filter by Font; look for files named like fa-solid-900.woff2, icomoon, MaterialIcons.
  • Check the CSS cost. Icon font stylesheets often contain hundreds of .icon-x:before { content: "\f123" } rules; check their size in Coverage.
  • Watch the first paint. On a throttled load, are icons missing or showing as boxes before popping in?
  • Check accessibility. Run axe: icon elements without accessible names or with announced private-use characters.

Root Cause Analysis

1. Font loading on the critical path. Icons cannot render until the font arrives, and the font is discovered only after its stylesheet loads.

2. Swap behaviour. With fallback rendering, glyph widths differ, and buttons or text containing icons reflow when the real font arrives.

3. Unused glyphs. Hundreds of icons ship for the handful a page uses.

4. Semantics. Icons in pseudo-elements carry no accessible name, and private-use code points can be read aloud.

Critical-path cost of icons on a header + navigation Bar chart comparing bytes on the critical path for header icons delivered via an icon font and via SVG. Critical-path cost of icons on a header + navigation Icon font WOFF2 78KB Icon font CSS 31KB SVG sprite (12 icons) 3.4KB

Step-by-Step Migration

1. Inventory the icons actually used

Grep templates for icon class names (fa-, icon-, material-icons text content) and list the distinct icons per area of the site. Most sites use a small fraction of the font's glyphs.

bash
grep -rhoE 'fa-[a-z0-9-]+' src/ | sort | uniq -c | sort -rn | head -50
# trade-off: dynamically built class names (e.g. `fa-${name}`) will not appear
# in a grep; search for the patterns that build them too.

2. Build an SVG icon set for those icons

Most icon libraries publish SVG sources (Font Awesome, Material Symbols, Lucide, Heroicons). Export the used icons, optimise with SVGO, and build a sprite or per-icon components.

3. Introduce an icon component that supports both systems

jsx
// Icon.jsx — renders SVG when available, falls back to the font during migration.
const SVG_ICONS = new Set(['search', 'cart', 'user', 'menu', 'close', 'star']);
export function Icon({ name, label, size = 20 }) {
  if (SVG_ICONS.has(name)) {
    return React.createElement('svg', { width: size, height: size, role: label ? 'img' : undefined,
      'aria-label': label, 'aria-hidden': label ? undefined : true },
      React.createElement('use', { href: `/icons/sprite.7c1d.svg#${name}` }));
  }
  return <i className={`fa fa-${name}`} aria-hidden={!label} aria-label={label} />;
}
// trade-off: running both systems temporarily keeps the font on the page;
// the win arrives only when the last font icon on a template is gone.

4. Migrate template by template, then remove the font

Convert high-traffic templates first; when a template uses no font icons, stop loading the icon font CSS on it. When no template does, delete the font and stylesheet and add a CI check that fails if they return.

Incremental icon font migration Five steps to migrate from an icon font to SVG icons without a big-bang rewrite. Incremental icon font migration Inventory used icons Usually a few dozen of hundreds Build an optimised SVG set Sprite or components from the library's SVG sources Dual-mode Icon component SVG where available, font as fallback Migrate top templates first Drop the font CSS per template when unused Remove the font everywhere CI check prevents its return 1 2 3 4 5

Verification

On migrated templates, the Network panel should show no icon font or icon CSS requests. Icons should render in the first paint at their final size, with no layout shift (check Layout Shift Regions). Accessibility checks should show decorative icons hidden and meaningful icons labelled. Compare LCP and CLS on migrated templates; removing a render-blocking icon stylesheet often improves LCP by 100–300ms on mobile.

Worked Example: A Banking Portal

A banking portal loaded a 120KB custom icon font plus a 36KB stylesheet on every page, with font-display: block; on slow connections, navigation icons and buttons with icon-only labels were invisible for up to three seconds, and screen reader users heard private-use characters. The inventory found 58 icons in use. The team shipped an SVG sprite (6KB), an Icon component with proper labels, and migrated over three sprints. Removing the font eliminated 156KB from the critical path, LCP p75 improved by 260ms, CLS from button reflows dropped from 0.06 to near zero, and accessibility audits passed for the first time.

Accessibility Gains From the Migration

The migration is also an accessibility fix, and worth planning as one. Decide for each icon whether it is decorative (aria-hidden="true", no label) or meaningful (an accessible name via aria-label on the control, or role="img" with a label on the SVG). Icon-only buttons need a label on the button. SVG also respects forced-colours modes (Windows High Contrast) when drawn with currentColor, which icon fonts often fail. Run an accessibility audit before and after; the improvement is usually as visible as the performance one.

Common Mistakes

  • Converting all icons at once. Large, risky change; migrate incrementally.
  • Inlining every icon. Repeated icons should use a sprite to avoid DOM bloat.
  • Losing icon sizing. Font icons sized by font-size; SVG icons need explicit width/height or 1em sizing to match.
  • Forgetting dynamic icon names. Icons chosen at runtime must be in the sprite.

Edge Cases

Icons in CSS pseudo-elements. Replace :before icons with inline elements or CSS mask-image using the SVG file.

Third-party widgets using the same font. Some embedded tools rely on your icon font; check before removing it globally.

Ligature-based fonts (Material Icons). Text like shopping_cart renders as an icon only after the font loads; without the font it shows the word — a long, layout-shifting string. Migrating these is especially valuable.

Email templates. Email clients have limited SVG support; keep raster icons there.

FAQ

Can I subset the icon font instead of migrating?

Subsetting reduces bytes, and is a reasonable stopgap, but the font still loads late, risks invisible or shifting icons, and keeps the accessibility problems. Migration solves all of them.

Is a sprite or inline SVG better for migrated icons?

A sprite for most icons, inline for a few that need part-level styling or animation. See inline SVG vs sprite vs img.

How do I size SVG icons like font icons?

Set width: 1em; height: 1em on the SVG so it scales with the surrounding font size, and vertical-align: -0.125em (or similar) to align with text baselines.

Will SVG icons look different from the font?

Slightly, in anti-aliasing and hinting, since fonts get text rendering treatment. Most libraries' SVG versions are drawn to look the same; compare at common sizes.

Does Font Awesome support SVG directly?

Yes — it provides SVG sprites, per-icon SVG files and framework components; avoid its JavaScript-based SVG replacement on large pages, which scans and rewrites the DOM at runtime.

How do I prevent the font from coming back?

Add a CI check that fails if the icon font's CSS or font file appears in the build output or is requested on key templates in a headless test.