Building a scalable icon system
Case study · Figma icon library, naming conventions, and an SVG-to-React delivery pipeline for 400+ icons across web and mobile.
The problem
Every product team was drawing its own icons. The result was three visual styles, four stroke weights, inconsistent 24 px grids, and duplicate SVGs shipping in every bundle. Engineers hard-coded hex colors so nothing responded to dark mode. We needed one scalable icon system that designers could contribute to and engineers could consume without thinking.
Design principles
- One 24×24 grid, 1.5 px stroke, 2 px inner padding — always.
- Optical corrections over mathematical purity: a circle is 22 px, not 24 px.
- Two weights only:
outlineandsolid. No duotone drift. - Icons are stateless. Color and size come from the parent, never the SVG.
Figma architecture
Each icon is a single component with two variants (outline, solid) and one boolean property (bg). The library exposes ~200 published components, but the total surface area for consumers is one component per concept — variants collapse the picker down to the shape, not the weight.
Naming follows a strict pattern so search and CLI generation stay predictable:
icon / [category] / [name] e.g. icon/nav/arrow-right icon / [category] / [name].solid e.g. icon/nav/arrow-right.solid
Naming conventions
- kebab-case, singular nouns —
bookmark, notbookmarks. - Direction suffixes are cardinal:
-up,-down,-left,-right. - Never encode color, size, or state in the name —
check, notcheck-green-16. - Aliases live in a JSON map, not as duplicate components.
SVG-to-React delivery pipeline
A nightly job pulls the Figma library via the REST API, runs each node through SVGO with a shared preset, and emits a tree-shakeable React package. Every icon becomes a typed component whose size, color, and title props are uniform.
Figma library ↓ figma-api export (nightly) SVG source (svgo → cleaned, 24×24) ↓ @svgr/core with custom template React components (@brand/icons) ↓ changesets → npm Web app, mobile RN app, marketing site
Every consumer imports the same package, so <ArrowRight /> in the web app and <ArrowRight /> in React Native render pixel-identical icons. Bundle size stays flat because unused icons tree-shake at the import boundary.
Governance
New icons ship through a PR template: reference screenshot, grid check, dark-mode check, and a duplicate-search against the library. A weekly triage merges accepted icons and cuts a patch release. In twelve months the library grew from 60 to 420 icons with zero visual regressions.
Results
- 420 icons, one design system, three platforms.
- ~38 kb removed from the web bundle after deduping legacy SVGs.
- New-icon lead time down from ~5 days to same-day merge.
- Zero hard-coded colors in shipped icons; full dark-mode parity.
Want the deeper walkthrough or the Figma file structure? Get in touch from the portfolio.