web3icons

Icon fonts vs SVG icons

Why icon fonts gave way to SVG, covering accessibility, color and rendering problems, where icon fonts still make sense, and how to migrate step by step.

5 min read

For much of the 2010s, the usual way to put icons on a website was a font. You loaded one file, added a class like icon-wallet, and a glyph appeared, sized with font-size and colored with color. Icon fonts were a clever answer to the tools of the time. SVG has since become the default, and the reasons are worth knowing whether you maintain an older codebase or are choosing how to ship a new set.

How icon fonts work

An icon font maps each icon to a character. Usually that character sits in Unicode's Private Use Area (U+E000 to U+F8FF), a range with no standard meaning, and CSS inserts it with a pseudo-element:

@font-face {
  font-family: 'AppIcons';
  src: url('/fonts/app-icons.woff2') format('woff2');
  font-display: block;
}

.icon {
  font-family: 'AppIcons';
  font-style: normal;
  line-height: 1;
}

.icon-wallet::before {
  content: '\e901';
}

Some icon fonts use ligatures instead, so the text wallet is drawn as a wallet icon. Either way, the browser treats the icon as text, and that's the root of most of the problems.

Why they fell out of favor

Accessibility. Because the icon is text, assistive technology reads it as text. A Private Use Area character may be announced as nothing, as an unknown symbol or as something unrelated, depending on the screen reader. Ligature fonts are read as the underlying word, which can produce announcements like "copy Copy" beside a visible label. Fixing this means adding aria-hidden="true" to every decorative icon and a separate accessible name wherever the icon carries meaning, and those are easy to forget.

Failure modes. When the font doesn't load, because of a blocked request, a slow network, a content blocker or a user who overrides site fonts for readability, people see empty boxes, odd characters or the raw ligature words. With font-display: block, icons stay invisible until the font arrives. An SVG that fails to load affects one icon, not all of them.

One color per glyph. A glyph takes a single color from CSS color. Multicolor icons need several glyphs stacked on top of each other, one per color. Color font formats exist, but they're rarely used for icon fonts and browser support has varied. For crypto logos, which are mostly multicolor brand marks, this alone rules fonts out.

Text rendering. Glyphs are positioned like letters: they sit on a baseline, inherit line-height, and get the platform's text anti-aliasing. Icons end up a pixel off vertically, look slightly bolder or softer on some systems, and are hard to align exactly with non-text elements.

All or nothing. The whole font downloads even if a page uses five icons, unless you subset it. A font with 2,000 crypto logos would be a heavy download for every page that shows one of them. With SVG files, each page fetches only the icons it displays.

Icon font SVG
Colors One per glyph Any
Accessible by default No, read as text With alt text, or hidden when decorative
When loading fails Every icon breaks One icon breaks
Load only what's used Only with subsetting Yes
Alignment Text baseline and line-height Box model, exact
Styling color, font-size currentColor, width and height, CSS on parts of inline SVG

Where icon fonts still appear

You'll still find icon fonts in older projects, CMS themes and plugins, and code built on earlier versions of popular frameworks. Bootstrap 3, for example, shipped an icon font called Glyphicons, which Bootstrap 4 dropped.

There's also a modern case: variable icon fonts. Google's Material Symbols is distributed as a variable font with axes for fill, weight, grade and optical size, so one file can render many styles, and switching an icon from outlined to filled is a CSS change. For a single-color UI icon set used heavily across an app, that's a genuine advantage. It doesn't help with brand logos, which need their own shapes and colors.

Migrating to SVG

A migration is mostly mechanical once you've built one small component.

1. Take an inventory. Search the codebase for the icon class prefix and list the names in use. Most projects use a fraction of the font.

grep -rhoE 'icon-[a-z0-9-]+' src | sort | uniq -c | sort -rn

2. Get the SVG versions. Most icon fonts were generated from SVG sources, so check whether the set publishes them. For crypto logos, take SVG files from an icon library or the project's press kit rather than converting font glyphs.

3. Write one component. For single-color interface icons, an SVG sprite with <use> keeps markup small, and fill="currentColor" lets icons follow the text color the way font icons did. Sizing in em keeps them scaling with the surrounding text. The guide on SVG sprites shows how to build the sprite file.

type IconProps = { name: string; label?: string }

export function Icon({ name, label }: IconProps) {
  return (
    <svg
      width="1em"
      height="1em"
      fill="currentColor"
      style={{ verticalAlign: '-0.125em' }}
      role={label ? 'img' : undefined}
      aria-label={label}
      aria-hidden={label ? undefined : true}
    >
      <use href={`/icons/sprite.svg#${name}`} />
    </svg>
  )
}

The vertical-align offset sits the icon on the text baseline, close to where font icons used to sit. For multicolor logos, use a plain <img> with the SVG file instead.

4. Replace usages, most common first. For each one, decide whether the icon is decorative, next to a visible label, so it should stay hidden, or the only content of a control, so it needs a name. Icon-only buttons are where font icons most often left screen reader users with nothing; see icon-only buttons.

// Before
<button className="btn"><i className="icon icon-copy" /></button>

// After
<button className="btn" aria-label="Copy address"><Icon name="copy" /></button>

5. Remove the font once nothing references it: the @font-face rule, the icon classes, any preload link and the font files. Check the Network panel to confirm it's no longer requested.

6. Check alignment and color at each text size you use. SVG icons don't inherit the font's line-height quirks, so offsets that were added to compensate for the font may now push icons out of place. For the color side, the guide on currentColor explains how single-color SVGs follow the surrounding text.

More in Performance

All guides