$npx -y skills add dylantarre/design-system-skills --skill icon-systemImplements scalable icon systems with SVG sprites or React/Vue components. Use when setting up icon libraries, creating icon sizing tokens, optimizing SVGs, or building accessible icon buttons.
| 1 | # Icon System |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Implement a scalable icon system with SVG sprites or icon components, proper sizing tokens, consistent stroke widths, and accessibility. Covers both sprite-based and component-based approaches. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Setting up icons for a new design system |
| 10 | - Converting icon fonts to SVG |
| 11 | - Creating an icon component library |
| 12 | - Establishing icon sizing standards |
| 13 | - Making icons accessible |
| 14 | |
| 15 | ## Quick Reference: Approaches |
| 16 | |
| 17 | | Approach | Best For | Bundle Size | Styling | |
| 18 | |----------|----------|-------------|---------| |
| 19 | | SVG Sprite | Large icon sets, caching | One request | CSS limited | |
| 20 | | Inline SVG Components | Tree-shaking, full control | Per-icon | Full CSS | |
| 21 | | Icon Font | Legacy support | One request | Limited | |
| 22 | | External SVG | Simple sites | Per-icon | Limited | |
| 23 | |
| 24 | ## The Process |
| 25 | |
| 26 | 1. **Choose approach**: Sprite vs components based on project needs |
| 27 | 2. **Define size scale**: Icon size tokens (xs, sm, md, lg, xl) |
| 28 | 3. **Establish stroke width**: Consistent line weights |
| 29 | 4. **Create component**: Wrapper with accessibility |
| 30 | 5. **Build tooling**: Optimization, sprite generation |
| 31 | |
| 32 | --- |
| 33 | |
| 34 | ## Size Tokens |
| 35 | |
| 36 | ### Icon Size Scale |
| 37 | |
| 38 | ```css |
| 39 | :root { |
| 40 | /* Icon sizes - match common UI patterns */ |
| 41 | --icon-size-xs: 0.75rem; /* 12px - inline text */ |
| 42 | --icon-size-sm: 1rem; /* 16px - small buttons */ |
| 43 | --icon-size-md: 1.25rem; /* 20px - default */ |
| 44 | --icon-size-lg: 1.5rem; /* 24px - large buttons */ |
| 45 | --icon-size-xl: 2rem; /* 32px - feature icons */ |
| 46 | --icon-size-2xl: 2.5rem; /* 40px - hero icons */ |
| 47 | --icon-size-3xl: 3rem; /* 48px - illustrations */ |
| 48 | } |
| 49 | ``` |
| 50 | |
| 51 | ### Tailwind Config |
| 52 | |
| 53 | ```js |
| 54 | // tailwind.config.js |
| 55 | module.exports = { |
| 56 | theme: { |
| 57 | extend: { |
| 58 | width: { |
| 59 | 'icon-xs': '0.75rem', |
| 60 | 'icon-sm': '1rem', |
| 61 | 'icon-md': '1.25rem', |
| 62 | 'icon-lg': '1.5rem', |
| 63 | 'icon-xl': '2rem', |
| 64 | }, |
| 65 | height: { |
| 66 | 'icon-xs': '0.75rem', |
| 67 | 'icon-sm': '1rem', |
| 68 | 'icon-md': '1.25rem', |
| 69 | 'icon-lg': '1.5rem', |
| 70 | 'icon-xl': '2rem', |
| 71 | }, |
| 72 | }, |
| 73 | }, |
| 74 | }; |
| 75 | ``` |
| 76 | |
| 77 | ### JSON Tokens |
| 78 | |
| 79 | ```json |
| 80 | { |
| 81 | "icon": { |
| 82 | "size": { |
| 83 | "xs": { "value": "0.75rem", "description": "12px - inline" }, |
| 84 | "sm": { "value": "1rem", "description": "16px - small buttons" }, |
| 85 | "md": { "value": "1.25rem", "description": "20px - default" }, |
| 86 | "lg": { "value": "1.5rem", "description": "24px - large buttons" }, |
| 87 | "xl": { "value": "2rem", "description": "32px - feature icons" } |
| 88 | }, |
| 89 | "stroke": { |
| 90 | "thin": { "value": "1" }, |
| 91 | "regular": { "value": "1.5" }, |
| 92 | "medium": { "value": "2" }, |
| 93 | "bold": { "value": "2.5" } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | ``` |
| 98 | |
| 99 | --- |
| 100 | |
| 101 | ## Approach 1: Inline SVG Components (Recommended) |
| 102 | |
| 103 | Best for React, Vue, Svelte. Tree-shakeable, full styling control. |
| 104 | |
| 105 | ### React Icon Component |
| 106 | |
| 107 | ```tsx |
| 108 | // components/Icon.tsx |
| 109 | import { forwardRef, SVGProps } from 'react'; |
| 110 | import clsx from 'clsx'; |
| 111 | |
| 112 | type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; |
| 113 | |
| 114 | interface IconProps extends SVGProps<SVGSVGElement> { |
| 115 | size?: IconSize; |
| 116 | label?: string; // Accessible label |
| 117 | } |
| 118 | |
| 119 | const sizeMap: Record<IconSize, string> = { |
| 120 | xs: 'w-3 h-3', // 12px |
| 121 | sm: 'w-4 h-4', // 16px |
| 122 | md: 'w-5 h-5', // 20px |
| 123 | lg: 'w-6 h-6', // 24px |
| 124 | xl: 'w-8 h-8', // 32px |
| 125 | }; |
| 126 | |
| 127 | export const Icon = forwardRef<SVGSVGElement, IconProps>( |
| 128 | ({ size = 'md', label, className, children, ...props }, ref) => { |
| 129 | return ( |
| 130 | <svg |
| 131 | ref={ref} |
| 132 | xmlns="http://www.w3.org/2000/svg" |
| 133 | viewBox="0 0 24 24" |
| 134 | fill="none" |
| 135 | stroke="currentColor" |
| 136 | strokeWidth="2" |
| 137 | strokeLinecap="round" |
| 138 | strokeLinejoin="round" |
| 139 | className={clsx(sizeMap[size], className)} |
| 140 | aria-hidden={!label} |
| 141 | aria-label={label} |
| 142 | role={label ? 'img' : 'presentation'} |
| 143 | {...props} |
| 144 | > |
| 145 | {children} |
| 146 | </svg> |
| 147 | ); |
| 148 | } |
| 149 | ); |
| 150 | ``` |
| 151 | |
| 152 | ### Individual Icon Components |
| 153 | |
| 154 | ```tsx |
| 155 | // icons/ChevronDown.tsx |
| 156 | import { Icon, IconProps } from '../Icon'; |
| 157 | |
| 158 | export function ChevronDown(props: IconProps) { |
| 159 | return ( |
| 160 | <Icon {...props}> |
| 161 | <polyline points="6 9 12 15 18 9" /> |
| 162 | </Icon> |
| 163 | ); |
| 164 | } |
| 165 | |
| 166 | // icons/Check.tsx |
| 167 | export function Check(props: IconProps) { |
| 168 | return ( |
| 169 | <Icon {...props}> |
| 170 | <polyline points="20 6 9 17 4 12" /> |
| 171 | </Icon> |
| 172 | ); |
| 173 | } |
| 174 | |
| 175 | // icons/X.tsx |
| 176 | export function X(props: IconProps) { |
| 177 | return ( |
| 178 | <Icon {...props}> |
| 179 | <line x1="18" y1="6" x2="6" y2="18" /> |
| 180 | <line x1="6" y1="6" x2="18" y2="18" /> |
| 181 | </Icon> |
| 182 | ); |
| 183 | } |
| 184 | ``` |
| 185 | |
| 186 | ### Icon Index with Tree-Shaking |
| 187 | |
| 188 | ```tsx |
| 189 | // icons/index.ts |
| 190 | export { ChevronDown } from './ChevronDown'; |
| 191 | export { Check } from './Check'; |
| 192 | export { X } from './X'; |
| 193 | export { Search } from './Search'; |
| 194 | // ... etc |
| 195 | |
| 196 | // Usage - only imports what's used |
| 197 | import { ChevronDown, Check } from '@/icons'; |
| 198 | ``` |
| 199 | |
| 200 | ### Usage |