DEV Community

Phaneendra Kanduri
Phaneendra Kanduri

Posted on

Your Icon Strategy Is a Hidden Tech Debt Bomb

Three approaches to scaling icons across hundreds of components. Two of them will cost you.


Why Icons Are Never Just Icons

Icons feel like a solved problem. Pick a library, drop them in, move on.

That works fine at 10 components. At 100 components, you start feeling friction. At 500 components with 100 icons across multiple feature modules, your icon strategy is either invisible infrastructure or a daily source of pain.

Most teams don't make a deliberate icon decision. They inherit one. And by the time the problems show up, the icons are everywhere.

This article covers three approaches, what breaks in each, and which one actually holds up in a large enterprise SaaS context.


Approach 1: Font-Based Icons

This is the most common starting point. Libraries like Font Awesome, Material Icons, and Google's own icon font work by mapping icon names to glyphs inside a custom font file. You reference them by class name or by writing the icon name as text.

<span class="material-icons">save</span>
Enter fullscreen mode Exit fullscreen mode

It feels clean. One CDN link, infinite icons, no SVG anywhere.

Where It Breaks

Font icons are network-dependent by design. The font file has to load before any icon renders.

I've seen this fail on Google's own properties. When the font hasn't loaded yet, the icon name renders as plain text. A button meant to show a save icon shows the word "save" instead. On a slow connection or a constrained device, this isn't an edge case — it's the experience.

For a public-facing marketing page, that might be acceptable. For an enterprise SaaS product used daily by thousands of users, it is not. Your users will see broken UI. And it will happen precisely when their network or device is already under stress.

Beyond rendering, font icons carry another problem: you load the entire font file even if you use 12 icons. You cannot tree-shake a font. Every icon the library ships is in the bundle whether your product uses it or not.

Verdict: Rules itself out for enterprise. Network dependency and no tree-shaking are disqualifying at scale.


Approach 2: Inline SVG

The natural response to font icon failures is to go directly to SVG. SVGs are vectors, they render perfectly at any size, they don't depend on a font file, and they're just markup.

So you paste the SVG directly into your component template.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
  <path fill="#333333" class="icon-primary" d="M17 3H5a2 2 ..."/>
</svg>
Enter fullscreen mode Exit fullscreen mode

It works. Until it doesn't.

Where It Breaks

The first problem is duplication. If the save icon appears in 15 components, the SVG markup is copy-pasted into 15 files. When the design team updates the icon — corrects a path, adjusts stroke width, updates the color — you're hunting across 15 files to apply the change. Miss one and you have an inconsistency that will take weeks to notice.

The second problem is that colors and classes are often hardcoded inside the SVG markup itself. fill="#333333" is not a variable. It's a value. Changing it requires touching every instance. Creating a variant — same icon, different color for a different state — means another copy.

The third problem is migration. When this pattern has been in use for a year across a large codebase, moving away from it is not a refactor. It's an excavation. Every file that inlined SVG needs to be found, assessed, and updated individually.

I tried this approach. The duplication was immediate and obvious. The design team updated an icon and the inconsistency spread faster than we could track it.

Verdict: Acceptable for small projects with few icons. Breaks down fast at scale. Migration cost is high.


Approach 3: SVG as Standalone Components

This is the approach that holds up.

Each icon is its own component. The SVG lives inside that component. Props control the variants — size, color, stroke, anything that needs to change between usages.

// SaveIcon component (framework-agnostic concept)

interface IconProps {
  size?: number;
  color?: string;
  strokeWidth?: number;
}

// Template / JSX
<svg
  xmlns="http://www.w3.org/2000/svg"
  [width]="size"
  [height]="size"
  viewBox="0 0 24 24"
>
  <path [attr.fill]="color" d="M17 3H5a2 2 ..." />
</svg>
Enter fullscreen mode Exit fullscreen mode

Every consumer of this icon imports the component and passes props. No SVG markup in the consumer's file. No duplication.

// Consumer
<SaveIcon size={20} color="var(--icon-primary)" />
Enter fullscreen mode Exit fullscreen mode

Why This Wins at Scale

Maintainability. When the design team updates the save icon, you update one file. One component. Every consumer gets the update automatically. No hunting, no inconsistency.

Variants without duplication. Need the same icon in a disabled state, a hover state, an error state? Pass a prop. Don't copy the file.

Bundle control. Each icon is a discrete module. If a feature only needs 10 icons, it imports 10 components. Nothing else ships with it. When I ran a module visualizer on the codebase after this migration, I could see exactly which icons each module imported — and nothing extra. Around 100 icons across a 500-component codebase, with each module importing only what it actually used.

Migration path. Because each icon is a named, importable component, replacing or updating the underlying SVG is a single-file change. Renaming or deprecating an icon is trackable through standard import analysis.

Design system alignment. Icon components sit naturally inside a shared component library or design system. They're versioned, documented, and owned — not scattered across feature folders as copy-pasted markup.

The Trade-off

Initial setup takes longer. You're creating a component per icon, not dropping in a CDN link. For a library of 100 icons, that's a one-time investment that pays back within the first few design updates.

If you're on a two-week spike with a throwaway prototype, this is overkill. For anything that will be maintained past the first quarter, it is the only approach that doesn't accumulate debt.


Decision Guide

Scenario Recommended Approach
Quick prototype, throwaway project Font icons or inline SVG
Small product, <20 icons, stable design Inline SVG
Enterprise SaaS, 50+ icons, active design iteration SVG components
Shared design system across multiple products SVG components, non-negotiable

The Pattern That Scales

Font icons trade render reliability for convenience. Inline SVG trades maintainability for simplicity. SVG components trade setup time for everything that matters after day one.

In a large B2B SaaS environment — multiple feature modules, active design iteration, a team that rotates — the icon strategy compounds quietly in the background. The wrong choice doesn't hurt immediately. It hurts when the design team updates a core icon and you realize it lives in 40 files. It hurts when a new engineer can't tell where an icon comes from or how to create a variant without duplicating it.

The component approach is more setup upfront. It is less everything else, forever.

Make the decision deliberately. Most teams don't, and they pay for it later.


Top comments (0)