Munch · Shared component library
SVG Icon System
A design system problem disguised as an icon problem.
- My role
- Owner of the shared SVG primitive and the icon conventions around it
- Stack
- ReactTypeScriptSVGTailwind CSSStorybook
The problem
Icons came out of Figma inconsistent. Different artboard sizes, different viewBox values, hardcoded fills. Dropping two icons next to each other in the UI produced two visibly different sizes, in two different colours, and fixing it meant hand-editing SVG markup per icon — which meant it drifted again the next time design shipped a batch.
The real problem wasn't any single icon. It was that there was no contract between design output and application code.
What I built
A shared SVG wrapper component that establishes that contract:
- Standardises the viewBox to a 24×24 grid, with an escape hatch for icons legitimately authored on a different grid.
- A named size scale (6xs through 6xl) mapped to Tailwind utilities, so sizing is a prop and not a magic number.
- fill='currentColor' throughout, so icons inherit colour from their parent — one less thing to pass, and theming works for free.
- shapeRendering='geometricPrecision' for crisp edges at small sizes.
Icons became a thin, predictable file: a named export, the path data, no styling decisions.
The interesting part
Design occasionally shipped icons drawn on a 16-unit grid, wrapped in a scale(1.5) group to fake compatibility with the 24 grid. That works visually but violates the pattern — and waiting for a redraw was a three-day turnaround.
I wrote a script to multiply the path coordinates by 1.5 mathematically and verified the output rendered pixel-identically at high resolution before shipping, so the transform wrapper could be removed without a visual diff.
Worth being honest about what that did and didn't fix: the coordinate scaling was lossless, but the original scale(1.5) had already inflated the stroke weight relative to natively-drawn 24-grid icons. Scaling the coordinates preserved that inflation rather than introducing it — correcting it properly needs inward curve offsetting on filled compound paths, or a redraw at source. I flagged it as a design-source fix rather than pretending a coordinate transform had solved it.
The lesson I took from it
I'd asserted a stroke weight was “standard” without measuring a native icon to check. A reviewer caught it. Measure first, then claim.
Outcome
Icons ship as data, not as styling decisions. Size and colour are props, and a new batch from design drops in without hand-editing markup.