How to Build an SVG Icon System That Actually Scales

For designers who manage icon libraries · June 2026 · 5 min read

A designer on my team once spent an entire afternoon manually exporting 48 icons as individual SVGs because our "icon system" was just a Figma file named "Icons - FINAL v3." Each icon was a different artboard size, some had strokes and some had fills, and the file names were things like "icon-arrow-right-UPDATED." When the frontend developer tried to use them, half the icons clipped at the viewBox boundary. I've been there. Here's the system I use now that stops this from happening.

Start With a Single Source of Truth

All icons live in one Figma file, on a grid of 24×24 artboards. Every icon uses the same base grid, same stroke width (1.5px), and same naming convention: icon-name--variant. No "FINAL" or "UPDATED" suffixes. The file is linked to the design system documentation in Notion or Storybook, not buried in someone's Drafts folder.

From that source file, icons are exported via a Figma plugin (SVG Export) with these settings checked: Inline SVG, Remove unused IDs, Minify output.

The Sprite Sheet Approach (for Traditional Sites)

For sites that aren't using a component framework, I create an SVG sprite sheet — a single file containing all icons as <symbol> elements, each with a unique ID. You include it once in the page (hidden) and reference individual icons with <use>:

<!-- Include once, usually in the header -->
<svg style="display:none" aria-hidden="true">
  <symbol id="icon-search" viewBox="0 0 24 24">
    <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16...">
  </symbol>
  <symbol id="icon-cart" viewBox="0 0 24 24">
    <path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7...">
  </symbol>
</svg>

<!-- Use anywhere on the page -->
<button aria-label="Search">
  <svg aria-hidden="true"><use href="#icon-search"/></svg>
</button>

The advantage: one HTTP request for all icons. The browser caches the sprite sheet, and every icon reference is instant. The viewBox on each <symbol> ensures scaling works correctly regardless of the <svg> container size.

The Component Approach (for React / Vue / Svelte)

In a component-based project, each icon becomes its own component. I use SVGR (React) or the equivalent for Vue/Svelte to auto-generate components from the exported SVG files. The build script runs on every push, so icons in production always match the Figma source.

// IconSearch.tsx — auto-generated from icon-search.svg
import { SVGProps } from 'react';
export const IconSearch = (props: SVGProps<SVGSVGElement>) => (
  <svg width={24} height={24} fill="none" {...props}>
    <path d="M15.5 14h-.79l-.28-.27..." />
  </svg>
);

The component accepts standard SVG props — className, color, size — so the consuming developer never needs to open the SVG file. They just import and use it like any other component.

What Goes Wrong When You Skip This

Every bad icon system I've inherited had the same problems:

Fixing this after the fact is tedious but worth it. I usually spend a day standardizing everything to 24×24, stroke-only (with fill="none"), and renaming files before generating components. The one-time cost pays off every time someone adds an icon without asking me.

My Go-To Workflow

  1. Figma: All icons on 24×24 grid, stroke-only, named with kebab-case.
  2. Export: SVG Export plugin → inline, minified, no unused IDs.
  3. Convert: Run the SVGs through SVG2PNG if I need PNG fallbacks for email or legacy browsers.
  4. Generate: SVGR (or equivalent) to create components with consistent props interfaces.
  5. Document: Storybook or a simple HTML page that renders every icon with its name, so designers and developers share the same reference.

The whole setup takes about 2 hours for a new project and saves dozens of hours over its lifetime. More importantly, it means nobody ever has to ask "where's the icon file?" — the answer is always the same place.

Jamie Park Written by Jamie Park — UI/UX Designer. designing interfaces and design systems for 8 years. Built SVG2PNG after getting tired of client assets on random servers. More about me →