Every web app needs icons. How you manage them at scale — that's where most teams make mistakes. This is the complete guide to building an SVG icon system that doesn't fall apart as your app grows.
Why SVG (Not Icon Fonts or PNG)
Icon fonts (FontAwesome, etc.) are the legacy approach. The problems:
- One broken font file breaks all icons
- Accessibility is terrible (screen readers read the unicode character)
- Crispy rendering requires specific font-smoothing hacks
- No multi-color support
PNG icons are dead for UI work. Blurry on Retina, can't be styled with CSS, fixed file per size.
SVG wins:
- Infinitely scalable, pixel-perfect on any screen
- Styleable with CSS (
currentColor, fill, stroke) - Accessible with proper ARIA labels
- Can animate with CSS or SMIL
- Single format handles all sizes
Where to Get Free SVG Icons
IconKing SVG Library — 254+ free SVG icons in flat and outline styles. Covers UI, social media, food, objects, and more. Downloadable as individual SVG, AI, or PNG files. No account required.
What sets IconKing apart: many icons have matching animated Lottie versions in the Lottie library — useful when you want an animated hover state that matches your static icon.
Other solid free sources:
- Heroicons (heroicons.com) — MIT, Tailwind-made, 292 icons
- Phosphor Icons (phosphoricons.com) — MIT, 1,248 icons, 6 weights
- Lucide (lucide.dev) — ISC, 1,400+ icons, React/Vue packages
- Tabler Icons (tabler.io/icons) — MIT, 5,000+ icons
Method 1: Inline SVG
Best for: small number of icons, need CSS styling
<!-- Inline the SVG directly -->
<button aria-label="Close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
The stroke="currentColor" means the icon inherits its color from the parent element's CSS color property — trivial theming.
Method 2: SVG Sprite
Best for: many icons, better performance (single HTTP request)
Build the sprite:
<!-- sprites.svg (hidden in HTML) -->
<svg style="display:none">
<defs>
<symbol id="icon-check" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" stroke="currentColor" fill="none" stroke-width="2"/>
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" stroke-width="2"/>
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" stroke-width="2"/>
</symbol>
</defs>
</svg>
Use the sprite:
<svg width="20" height="20" aria-label="Success" role="img">
<use href="#icon-check" />
</svg>
Method 3: React Icon Component
Best for: React apps, TypeScript, tree-shaking
// Icon.tsx
interface IconProps {
name: string;
size?: number;
color?: string;
className?: string;
}
const icons = {
check: <polyline points="20 6 9 17 4 12" stroke="currentColor" fill="none" strokeWidth={2}/>,
close: <><line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" strokeWidth={2}/>
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" strokeWidth={2}/></>,
};
export function Icon({ name, size = 20, className = '' }: IconProps) {
return (
<svg width={size} height={size} viewBox="0 0 24 24"
className={className} aria-hidden="true">
{icons[name]}
</svg>
);
}
Usage: <Icon name="check" size={16} />
When to Use Animated Icons
For hover states, loading states, or interactive transitions — static SVG isn't enough.
Lottie animations are the right choice here. The IconKing Lottie library has animated versions of many common UI icons.
Preview any animation at iconking.net/preview before using.
Edit colors to match your design system at iconking.net/editor.
Need the animated icon as a GIF for non-JS environments? iconking.net/tools/lottie-to-gif.
Accessibility Checklist
- Decorative icons:
aria-hidden="true" - Meaningful icons:
role="img"+aria-label="description" - Icon buttons: put
aria-labelon the<button>, not the SVG - Minimum tap target: 44x44px (can be larger than the visual icon)
- Sufficient color contrast: icons need 3:1 contrast ratio minimum
Optimizing SVG Files
Downloaded SVGs are often bloated with editor metadata. Before using in production, run through SVGO:
npm install -g svgo
svgo my-icon.svg -o my-icon-optimized.svg
Typical savings: 30-70% file size reduction. A 4KB SVG becomes 1.2KB.
What's your icon system setup? Share in the comments — especially interested in how teams handle this at scale.
Top comments (0)