Modern best practice leans heavily toward SVG. They’re flexible, accessible, and powerful — and in many cases, they’re absolutely the right choice.
But here’s the thing.
Sometimes you don’t need power.
You need simplicity.
You don’t need:
- A full icon library
- A component abstraction
- A build-time SVG loader
- Dozens (or hundreds) of unused icons in your bundle
You just need:
<span class="icon-search"></span>
When Icon Fonts Are Actually the Simpler Choice
For small UI icon sets — especially in server-rendered or static projects, a single .woff2 file + one CSS file can be simpler and smaller than pulling in an SVG library. Fonts are aggressively cached, require no JavaScript, and behave like text by default.
Fonts are:
Aggressively cached by browsers
Pure CSS (no JavaScript required)
Naturally scalable
Text-like by default (inherit size and color automatically)
Instead of importing a large icon package, a minimal SVG → font pipeline can be set up using Node.js.
No frameworks.
No bundlers.
Just a small build script and a folder of SVGs.
Let’s break it down.
The Stack
-
Node.js (ESM,
"type": "module") - svgtofont — the library that does the heavy lifting
npm install svgtofont --save-dev
Project Structure
icons/ ← drop your SVGs here
dist/ ← generated font + CSS lands here
scripts/
build-icons.mjs ← the build script
package.json
The Build Script
This is the entirety of scripts/build-icons.mjs:
import svgtofont from 'svgtofont';
import path from 'path';
import fs from 'fs';
const rootDir = process.cwd();
const ICONS_DIR = path.resolve(rootDir, 'icons');
const DIST_DIR = path.resolve(rootDir, 'dist');
const FONT_NAME = 'icons';
async function build() {
await svgtofont({
src: ICONS_DIR,
dist: DIST_DIR,
fontName: FONT_NAME,
classNamePrefix: 'icon', // ← generates .icon-search, .icon-cart, etc.
css: true, // ← auto-generate the CSS file
outSVGPath: false,
generateInfoData: false,
website: null,
emptyDist: true, // ← clear dist before each build
startUnicode: 0xe001, // ← start in the Private Use Area (PUA)
});
// svgtofont outputs some extra files we don't need — clean them up
const allowed = ['.ttf', '.woff', '.woff2', '.css'];
fs.readdirSync(DIST_DIR).forEach((file) => {
if (!allowed.some((ext) => file.endsWith(ext))) {
fs.unlinkSync(path.join(DIST_DIR, file));
}
});
console.log('✅ Icons built successfully.');
}
build().catch(console.error);
Wire it up in package.json:
{
"type": "module",
"scripts": {
"build:icons": "node scripts/build-icons.mjs"
}
}
Run it:
npm run build:icons
Output in dist/:
dist/
icons.css
icons.ttf
icons.woff
icons.woff2
What Gets Generated
The icons.css looks like this:
@font-face {
font-family: "icons";
src: url('icons.woff2') format('woff2'),
url('icons.woff') format('woff'),
url('icons.ttf') format('truetype');
}
[class^="icon-"], [class*=" icon-"] {
font-family: 'icons' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-cart-shopping::before { content: "\e001"; }
.icon-eye::before { content: "\e002"; }
.icon-facebook::before { content: "\e003"; }
.icon-house::before { content: "\e004"; }
.icon-plus::before { content: "\e005"; }
.icon-volume::before { content: "\e006"; }
Each icon name maps directly to the SVG filename (without extension). The ::before pseudo-element injects the glyph via a Unicode codepoint in the Private Use Area — same technique Font Awesome uses.
Using It in HTML
<link rel="stylesheet" href="./dist/icons.css" />
<span class="icon-search"></span>
<span class="icon-cart-shopping"></span>
Sizing and coloring works exactly like text:
.icon-search {
font-size: 24px;
color: #6152e8;
}
Used correctly, an SVG icon and a generated font icon can look visually identical in the UI. The difference is not in appearance, but in delivery — a single cached font file can sometimes be simpler and lighter than managing multiple inline SVGs.
Wrapping Up
The whole thing is about 35 lines of JavaScript.
It’s also important to use clean SVG files with a proper viewBox, simple path elements, and no embedded images. If you’re working with outline-style icons, convert strokes into filled outlines before generating the font.
Custom icon fonts are not a replacement for SVG in every situation. But for small, controlled icon sets in static or server-rendered projects, they can be a very lightweight, cache-friendly, and dependency-free solution.
The full source code GitHub
References -
Icons sourced from Font Awesome
icon-font vs svg Blog
The Developer's Guide to Icons: Icon Fonts vs. SVGs Blog

Top comments (0)