Icon Systems
Icon systems, SVG management, and scalable icon delivery pipelines for design systems
You are an expert in icon systems and SVG management for building and maintaining design systems.
## Key Points
- **Viewport size** — typically 24x24 or 20x20
- **Stroke width** — consistent across the set (e.g., 1.5px or 2px)
- **Padding** — 1-2px live area inset from the viewport edge
- **Style** — outlined, filled, or duo-tone (pick one primary style, offer variants)
- Use `currentColor` for stroke or fill so icons inherit the parent text color automatically and work seamlessly with theming.
- Automate the SVG-to-component pipeline in CI so designers can drop new SVGs into a folder and the icon library updates without manual work.
- Provide an accessible `label` prop — when set, render `role="img"` with `aria-label`; when absent, mark the icon as `aria-hidden="true"`.
- Shipping icons with hard-coded colors (e.g., `fill="#000"`) which break in dark mode and prevent consumers from styling them.
- Neglecting to optimize SVGs before bundling, resulting in bloated markup with editor metadata, hidden layers, and unnecessary precision in path data.
## Quick Example
```
icon-arrow-left.svg
icon-check-circle.svg
icon-chevron-down.svg
icon-file-text.svg
icon-user-plus.svg
```
```ts
// src/icons/index.ts
export { ArrowLeftIcon } from './ArrowLeftIcon';
export { CheckCircleIcon } from './CheckCircleIcon';
export { ChevronDownIcon } from './ChevronDownIcon';
// ...auto-generated
```skilldb get design-systems-skills/Icon SystemsFull skill: 214 linesIcon Systems — Design Systems
You are an expert in icon systems and SVG management for building and maintaining design systems.
Overview
An icon system provides a consistent, performant, and maintainable way to deliver hundreds of icons across a product. It covers the pipeline from designer-authored SVGs through optimization, packaging, and runtime rendering, ensuring every icon aligns to the same grid, stroke weight, and sizing conventions.
Core Philosophy
An icon system is infrastructure, not a folder of SVG files. The difference is the pipeline — a well-designed icon system takes raw designer-authored SVGs and automatically optimizes, normalizes, and packages them into consumable components. Designers drop files into a directory, CI runs the pipeline, and engineers import tree-shakeable icon components without touching SVG markup by hand.
Visual consistency is the primary value an icon system provides. When every icon shares the same viewport, stroke width, optical weight, and padding, they feel like they belong together regardless of who drew them. A 24x24 grid with 1.5px strokes and 2px padding is a common convention, but the specific values matter less than universal adherence to them. A single icon that uses 2px strokes in a set of 1.5px icons is immediately visible and undermines the entire system.
Icons must be color-agnostic by default. Using currentColor for stroke or fill ensures icons inherit their parent's text color, which makes them work seamlessly with dark mode, brand themes, and contextual coloring (success green, error red) without any icon-specific styling. Hardcoded colors in SVG source files are the single most common icon system defect.
Core Concepts
Icon Grid and Consistency
All icons in a system should share:
- Viewport size — typically 24x24 or 20x20
- Stroke width — consistent across the set (e.g., 1.5px or 2px)
- Padding — 1-2px live area inset from the viewport edge
- Style — outlined, filled, or duo-tone (pick one primary style, offer variants)
Delivery Strategies
| Strategy | Description | Best for |
|---|---|---|
| Inline SVG components | Each icon is a React/Vue component | Tree-shaking, SSR |
| SVG sprite sheet | Single <svg> with <symbol> defs | HTTP/1 sites, HTML-only |
| Icon font | Glyphs mapped to Unicode codepoints | Legacy support only |
| Dynamic import | Lazy-load icons by name | Large icon sets, code splitting |
Naming Conventions
Use a flat, descriptive naming scheme:
icon-arrow-left.svg
icon-check-circle.svg
icon-chevron-down.svg
icon-file-text.svg
icon-user-plus.svg
Prefix with icon-, use lowercase kebab-case, and avoid abbreviations.
Implementation Patterns
SVG Optimization Pipeline with SVGO
// svgo.config.js
module.exports = {
multipass: true,
plugins: [
'preset-default',
'removeDimensions',
{
name: 'addAttributesToSVGElement',
params: {
attributes: [
{ fill: 'none' },
{ stroke: 'currentColor' },
{ 'stroke-width': '1.5' },
{ 'stroke-linecap': 'round' },
{ 'stroke-linejoin': 'round' },
],
},
},
{
name: 'removeAttrs',
params: { attrs: ['class', 'data-name', 'id'] },
},
],
};
Code Generation: SVG to React Component
// scripts/generate-icons.js
const fs = require('fs');
const path = require('path');
const { optimize } = require('svgo');
const SVG_DIR = path.join(__dirname, '../assets/icons');
const OUT_DIR = path.join(__dirname, '../src/icons');
const toPascalCase = (str) =>
str.replace(/(^|-)(\w)/g, (_, __, c) => c.toUpperCase());
fs.readdirSync(SVG_DIR)
.filter((f) => f.endsWith('.svg'))
.forEach((file) => {
const name = toPascalCase(file.replace('icon-', '').replace('.svg', ''));
const raw = fs.readFileSync(path.join(SVG_DIR, file), 'utf8');
const { data } = optimize(raw);
const svgBody = data
.replace(/<svg[^>]*>/, '')
.replace('</svg>', '');
const component = `import type { SVGProps } from 'react';
export function ${name}Icon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
width="1em"
height="1em"
{...props}
>
${svgBody.trim()}
</svg>
);
}
`;
fs.writeFileSync(path.join(OUT_DIR, `${name}Icon.tsx`), component);
});
Wrapper Icon Component
import type { SVGProps } from 'react';
export interface IconProps extends SVGProps<SVGSVGElement> {
/** Icon size in px or rem — applied to width and height */
size?: number | string;
/** Accessible label; if omitted the icon is decorative (aria-hidden) */
label?: string;
}
export function Icon({
size = '1.25em',
label,
children,
...props
}: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={size}
height={size}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
role={label ? 'img' : 'presentation'}
aria-label={label}
aria-hidden={label ? undefined : true}
{...props}
>
{children}
</svg>
);
}
Barrel Export with Tree-Shaking
// src/icons/index.ts
export { ArrowLeftIcon } from './ArrowLeftIcon';
export { CheckCircleIcon } from './CheckCircleIcon';
export { ChevronDownIcon } from './ChevronDownIcon';
// ...auto-generated
Consumers import only what they use:
import { CheckCircleIcon } from '@acme/icons';
Best Practices
- Use
currentColorfor stroke or fill so icons inherit the parent text color automatically and work seamlessly with theming. - Automate the SVG-to-component pipeline in CI so designers can drop new SVGs into a folder and the icon library updates without manual work.
- Provide an accessible
labelprop — when set, renderrole="img"witharia-label; when absent, mark the icon asaria-hidden="true".
Common Pitfalls
- Shipping icons with hard-coded colors (e.g.,
fill="#000") which break in dark mode and prevent consumers from styling them. - Neglecting to optimize SVGs before bundling, resulting in bloated markup with editor metadata, hidden layers, and unnecessary precision in path data.
Anti-Patterns
-
Icon fonts in new projects. Icon fonts were a reasonable solution in the HTTP/1 era, but they have significant downsides: no tree-shaking, rendering as text (which affects accessibility), and FOIT (flash of invisible text) during loading. Use inline SVG components for all new work.
-
Hardcoded colors in SVG source files. Icons with
fill="#000000"orstroke="#333"baked into the paths cannot adapt to dark mode, brand themes, or contextual coloring. Every icon should usecurrentColorexclusively so it inherits its color from the parent element. -
No optimization pipeline. Shipping SVGs straight from Figma or Illustrator includes editor metadata, unnecessary groups, hidden layers, and excessive decimal precision in path data. This bloats the icon bundle and produces inconsistent markup across icons.
-
Inconsistent viewport sizes. Mixing 16x16, 20x20, and 24x24 icons in the same system forces consumers to remember which icon uses which size and manually adjust. Standardize on one viewport (or a clearly defined set with explicit size variants) and enforce it in the pipeline.
-
Missing accessibility handling. Icons used as standalone interactive elements (like an icon-only button) need an accessible label via
aria-label. Decorative icons alongside text needaria-hidden="true". An icon system that does not provide alabelprop and corresponding ARIA handling leaves accessibility to chance.
Install this skill directly: skilldb add design-systems-skills
Related Skills
Component Architecture
Component API design, composition patterns, and architecture for scalable design system components
Design System Governance
Versioning, contribution processes, and governance models for design system teams
Design Tokens
Design tokens for colors, spacing, typography, and other visual primitives in design systems
Motion Guidelines
Motion and animation guidelines, easing curves, and transition patterns for design systems
Responsive Patterns
Responsive design patterns, fluid layouts, and adaptive component strategies for design systems
Storybook Docs
Storybook documentation, visual testing, and interactive component cataloging for design systems