Why Custom Icon Fonts are the Ultimate Lightweight Icon Strategy

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Why Custom Icon Fonts are the Ultimate Lightweight Icon Strategy

    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:






    class="icon-search">










    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





    rel="stylesheet" href="./dist/icons.css" />

    class="icon-search">
    class="icon-cart-shopping">







    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. Use the right SVG format, keep the icon set minimal, and the setup stays simple and efficient.


    The full source code GitHub





    References -

    Icons sourced from Font Awesome

    icon-font vs svg blogBlog

    The Developer's Guide to Icons: Icon Fonts vs. SVGs Blog




    More...
Working...