| name | icon-component-library-builder |
| description | Scaffolds an SVG icon component system: a reusable Icon wrapper component,
a typed icon name union, an icon sprite approach or inline SVG approach, and
a usage guide. Use this skill whenever a user says "build an icon component",
"create an icon system", "make my SVGs reusable as components", "scaffold an
icon library in React", "convert these SVGs to components", "how do I manage
icons in my design system?", or "create a typed icon set". Also activate when
someone wants to standardize how icons are used across their app. Works with
React + TypeScript, Vue 3, or plain Web Components. Do NOT use for creating
or sourcing the actual SVG icon artwork (use an icon library like Lucide,
Phosphor, or Heroicons as the source).
|
Icon Component Library Builder
Scaffold a type-safe, accessible icon component system that scales from a handful
of icons to hundreds — with a single reusable wrapper component.
When to Use
- Standardizing icon usage across a design system or app
- Migrating from ad-hoc
<img> or raw <svg> icons to a typed component
- Building a custom icon set on top of Lucide, Heroicons, or custom SVGs
- Creating a self-contained icon package in a monorepo
When NOT to Use
- Sourcing or designing the actual SVG artwork
- Animated icons (see
animation-snippet-writer for animated SVG patterns)
- Image/raster icons (use
<img> — not SVG components)
Workflow
Step 1 — Understand the Requirements
Ask for:
- Icon source: Lucide / Heroicons / Phosphor / Custom SVG files
- Framework: React + TS / Vue 3 / Web Component
- Approach: Inline SVG component per icon, or single sprite sheet
- Scale: How many icons? (< 50: per-file; > 50: sprite or icon font)
- Use case: UI only, or also email/PDF (inline SVG is required for email)
Step 2 — Choose the Architecture
| Approach | When to use | Pros | Cons |
|---|
| Inline SVG per file | < 100 icons, React/Vue | Tree-shakeable, colorable via CSS, no extra request | Bundle size grows with count |
| SVG Sprite | 100+ icons, performance critical | Single HTTP request, cached | Not tree-shakeable, needs sprite generator |
| Icon font (Iconfont) | Legacy or cross-platform | Works everywhere | Blurry at small sizes, one color only |
Recommendation for most apps: Inline SVG per file with lazy loading.
Step 3 — Generate the Icon Component
React + TypeScript (Inline SVG approach):
export interface IconProps {
size?: number | string;
color?: string;
"aria-label"?: string;
className?: string;
strokeWidth?: number;
}
import { forwardRef } from "react";
import type { IconProps } from "./types";
interface IconWrapperProps extends IconProps {
children: React.ReactNode;
viewBox?: string;
}
export const Icon = forwardRef<SVGSVGElement, IconWrapperProps>(
(
{
size = 24,
color = "currentColor",
"aria-label": ariaLabel,
className,
children,
viewBox = "0 0 24 24",
strokeWidth = 2,
...props
},
ref
) => {
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox={viewBox}
fill="none"
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
role={ariaLabel ? "img" : "presentation"}
aria-label={ariaLabel}
aria-hidden={!ariaLabel}
className={className}
{...props}
>
{children}
</svg>
);
}
);
Icon.displayName = "Icon";
import { Icon } from "./Icon";
import type { IconProps } from "./types";
export function ChevronDownIcon(props: IconProps) {
return (
<Icon {...props}>
<path d="M6 9l6 6 6-6" />
</Icon>
);
}
export { ChevronDownIcon } from "./ChevronDown";
export { CheckIcon } from "./Check";
export { XIcon } from "./X";
export { MenuIcon } from "./Menu";
export type IconName = "chevron-down" | "check" | "x" | "menu";
Dynamic icon lookup by name:
import { lazy, Suspense } from "react";
import type { IconProps } from "./types";
import type { IconName } from "./index";
const iconMap: Record<IconName, React.LazyExoticComponent<React.FC<IconProps>>> = {
"chevron-down": lazy(() => import("./ChevronDown").then((m) => ({ default: m.ChevronDownIcon }))),
check: lazy(() => import("./Check").then((m) => ({ default: m.CheckIcon }))),
};
interface DynamicIconProps extends IconProps {
name: IconName;
}
export function DynamicIcon({ name, ...props }: DynamicIconProps) {
const IconComponent = iconMap[name];
return (
<Suspense
fallback={
<span
style={{
width: props.size,
height: props.size,
display: "inline-block",
}}
/>
}
>
<IconComponent {...props} />
</Suspense>
);
}
Step 4 — Generate a Build Script (for Custom SVG folders)
import fs from "fs";
import path from "path";
const SVG_DIR = "assets/icons";
const OUT_DIR = "src/icons";
const files = fs.readdirSync(SVG_DIR).filter((f) => f.endsWith(".svg"));
files.forEach((file) => {
const name = path.basename(file, ".svg");
const componentName =
name
.split("-")
.map((p) => p[0].toUpperCase() + p.slice(1))
.join("") + "Icon";
const svgContent = fs.readFileSync(path.join(SVG_DIR, file), "utf-8");
const pathMatch = svgContent.match(/<path[^>]+>/g) ?? [];
const paths = pathMatch.join("\n ");
const component = `// GENERATED — DO NOT EDIT
import { Icon } from './Icon';
import type { IconProps } from './types';
export function ${componentName}(props: IconProps) {
return (
<Icon {...props}>
${paths}
</Icon>
);
}
`;
fs.writeFileSync(path.join(OUT_DIR, `${componentName}.tsx`), component);
});
console.log(`Generated ${files.length} icon components`);
Step 5 — Usage Guide
import { ChevronDownIcon, CheckIcon } from '@/icons';
import { DynamicIcon } from '@/icons/DynamicIcon';
<ChevronDownIcon size={16} />
<button aria-label="Close">
<XIcon size={20} aria-label="Close" />
</button>
<CheckIcon size={24} color="#16A34A" />
<DynamicIcon name="menu" size={24} />
Output Format
Icon.tsx — base wrapper component
types.ts — shared props interface
- Individual icon files — 3–5 example icons
index.ts — barrel export with IconName type
DynamicIcon.tsx — lazy-loaded dynamic icon (optional)
- Build script — if generating from SVG folder
Safety & Confirmation
- Always set
aria-hidden on decorative icons and provide aria-label on standalone icon buttons.
- Don't inline SVG
fill colors hardcoded — use currentColor so icons inherit text color.
- For performance: don't import the full icon library — import individual icons only.
- Warn if using an SVG with an embedded
<style> block — it may conflict with global CSS.