@purposeinplay/feral-icons
v0.1.4
Published
Custom React icon library with tree-shaking support
Readme
@purposeinplay/feral-icons
Custom React icon library with full tree-shaking support and TypeScript autocomplete.
Features
- 🎯 Name-based API -
<FeralIcon name="camera" />- simple and clean - 🌲 Tree-shakable - Only imports what you use
- 📦 Tiny bundle - ~0.3KB per icon gzipped
- 🔷 TypeScript - Full type safety with icon name autocomplete
- ⚡ Fast - Optimized SVGs with SVGO
- 🎨 Customizable - Control size, color, and all SVG props
- 🔧 Easy to extend - Add new icons by dropping SVG files
Installation
npm install @purposeinplay/feral-icons
# or
yarn add @purposeinplay/feral-icons
# or
pnpm add @purposeinplay/feral-iconsUsage
Basic Usage
import { FeralIcon } from "@purposeinplay/feral-icons";
function App() {
return (
<>
<FeralIcon name="arrow-left" size={24} />
<FeralIcon name="camera" fill="red" />
<FeralIcon name="user" className="text-blue-500" fill="#3B82F6" />
</>
);
}With Dynamic Names
import { FeralIcon, type IconName } from "@purposeinplay/feral-icons";
function IconDisplay({ iconName }: { iconName: IconName }) {
return <FeralIcon name={iconName} size={24} />;
}
// Usage
<IconDisplay iconName="camera" />;With Tailwind CSS
Icons work seamlessly with Tailwind CSS color utilities:
// Using Tailwind text color classes (recommended)
<FeralIcon name="camera" className="text-blue-600" />
<FeralIcon name="user" className="text-red-500 hover:text-red-700" />
// Using Tailwind colors with fill prop
<FeralIcon name="arrow-left" fill="rgb(59 130 246)" />
// Responsive colors
<FeralIcon name="camera" className="text-blue-500 md:text-purple-500" />Pro tip: Since icons default to fill="currentColor", you can control their color with Tailwind's text-* utility classes!
Props
The FeralIcon component accepts these props:
name: IconName- Required. The icon name (e.g.,"camera","user","arrow-left")size?: number | string- Icon size (default: 24)fill?: string- Fill color (default: 'currentColor')className?: string- Additional CSS classes- All standard SVG attributes (
onClick,onMouseEnter, etc.)
Available Icons
arrow-left- Navigation arrow pointing leftcamera- Camera iconuser- User profile icon
All icon names are available as the IconName type for TypeScript autocomplete.
Tree-Shaking
This library is fully tree-shakable. Only imported icons are included in your bundle.
Example bundle impact:
- Importing all icons: ~2KB gzipped
- Importing single icon: ~0.3KB gzipped
Local Development & Testing
View Icons in Storybook
Run Storybook to browse and interact with all icons:
npm run storybookThis will open Storybook at http://localhost:6006 where you can:
- Browse all available icons
- Test different sizes and colors
- Try Tailwind CSS integration
- Copy code examples
- Test accessibility features
Test Locally with npm link
# In the @purposeinplay/feral-icons directory
npm link
# In your app directory
npm link @purposeinplay/feral-iconsUse in Your App
import { FeralIcon } from "@purposeinplay/feral-icons";
function MyComponent() {
return <FeralIcon name="arrow-left" size={32} fill="#3B82F6" />;
}Adding New Icons
Quick Start
Add your SVG file to the
svgs/directory# Example: Add a new icon called "heart" cp ~/Downloads/heart.svg svgs/heart.svgBuild the library
npm run buildUse your new icon
<FeralIcon name="heart" size={24} />
That's it! The build process automatically:
- ✅ Optimizes the SVG
- ✅ Generates a React component (
Heart.tsx) - ✅ Adds it to the icon registry
- ✅ Updates TypeScript types for autocomplete
SVG Requirements
Your SVG files should follow these guidelines:
Required:
- ✅ 19x19 viewBox (e.g.,
viewBox="0 0 19 19") - ✅ Path-based design with filled paths (not strokes)
- ✅ Kebab-case naming (e.g.,
arrow-left.svg,user-circle.svg)
Recommended:
- ✅ Use
fill="currentColor"or no fill attribute (will inherit from parent) - ✅ Remove any
strokeattributes (optimizer will strip them anyway) - ✅ Remove unnecessary attributes like
id,class,data-*
Example SVG:
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19">
<path d="M9.5 2L2 6v8c0 4.44 3.07 8.59 7.5 9.6 4.43-1.01 7.5-5.16 7.5-9.6V6l-7.5-4z"/>
</svg>Icon Naming Convention
SVG filenames are converted to icon names:
arrow-left.svg→name="arrow-left"user-circle.svg→name="user-circle"chevron-down.svg→name="chevron-down"
Component names are PascalCase internally:
arrow-left.svg→ArrowLeftcomponentuser-circle.svg→UserCirclecomponent
Development Scripts
Main Commands
# Full build (clean + generate + compile)
npm run build
# Clean generated files (icons, registry, index)
npm run clean
# Generate components from SVGs (without building)
npm run generate
# Build and watch for changes
npm run devDevelopment Workflow
Adding/updating icons:
# 1. Add SVG to svgs/ directory
cp new-icon.svg svgs/
# 2. Full rebuild
npm run build
# 3. Icons are now available!During development:
# Watch mode - rebuilds on changes
npm run dev
# In another terminal, run Storybook
npm run storybookTesting Changes Locally
# In the icon library directory
npm link
# In your app directory
npm link @purposeinplay/feral-icons
# Make changes, rebuild, and test
npm run buildHow It Works
Architecture Overview
User Code Internal System SVG Files
───────── ──────────────── ─────────
<FeralIcon → iconRegistry svgs/
name="camera" { ├── arrow-left.svg
size={24} 'camera': Camera, ├── camera.svg
/> 'user': User, └── user.svg
... ↓
} [Build Process]
↓ ↓
Camera component Generated Components
renders with props ├── ArrowLeft.tsx
├── Camera.tsx
└── User.tsxBuild Process
When you run npm run build, here's what happens:
- Clean - Removes old generated files
- Generate - For each SVG in
svgs/:- Optimizes SVG with SVGO (removes strokes, dimensions, etc.)
- Extracts SVG path content
- Generates React component file
- Adds to icon registry
- Updates TypeScript types
- Compile - TypeScript generates
.d.tsfiles - Bundle - Vite creates ESM output with tree-shaking
File Structure
feral-icon-pack/
├── svgs/ # Source SVG files (you add here)
│ ├── arrow-left.svg
│ ├── camera.svg
│ └── user.svg
├── src/
│ ├── icons/ # Generated (gitignored)
│ │ ├── ArrowLeft.tsx
│ │ ├── Camera.tsx
│ │ └── User.tsx
│ ├── shared/
│ │ ├── Icon.tsx # Base SVG wrapper
│ │ └── types.ts # Internal types
│ ├── iconRegistry.ts # Generated (gitignored)
│ ├── FeralIcon.tsx # Public component
│ └── index.ts # Generated (gitignored)
├── dist/ # Build output (gitignored)
│ ├── index.js
│ ├── index.d.ts
│ ├── FeralIcon.js
│ └── ...
└── scripts/
├── generate.ts # SVG → React converter
└── optimize.ts # SVGO configurationPackage Exports
Only these are exported publicly:
// From @purposeinplay/feral-icons
export { FeralIcon } from "./FeralIcon";
export type { FeralIconProps, IconName } from "./FeralIcon";Everything else (individual icon components, base Icon, internal types) stays internal for the registry system.
TypeScript Support
This library is written in TypeScript and includes full type definitions.
import { FeralIcon, type FeralIconProps, type IconName } from "@purposeinplay/feral-icons";
// Type for icon props
const CustomIconWrapper: React.FC<FeralIconProps> = (props) => {
return <FeralIcon {...props} />;
};
// Type for icon names (with autocomplete)
const iconName: IconName = "camera"; // TypeScript will suggest: "arrow-left" | "camera" | "user"
// Dynamic icon selection with type safety
function DynamicIcon({ name }: { name: IconName }) {
return <FeralIcon name={name} size={24} />;
}Available Types:
IconName- Union type of all valid icon names ("arrow-left" | "camera" | "user")FeralIconProps- Props interface for the FeralIcon component (name,size,fill,className, etc.)
Browser Support
- Modern browsers with ES2020 support
- React 18.0.0 or higher
- React 19.x supported
License
MIT
Contributing
Adding Icons
- Fork and clone this repository
- Add SVG files to
svgs/directory - Follow SVG requirements (24x24, fill-based, kebab-case)
- Build and test
npm run build npm run storybook - Commit and PR
git add svgs/your-icon.svg npm run commit # Use Commitizen for standardized commits
Commit Messages
This project uses Commitizen for standardized commit messages following the Conventional Commits specification.
Using Commitizen (recommended):
# Stage your changes
git add .
# Use commitizen to create the commit
npm run commitThis will prompt you with:
- Type: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scope: Optional (e.g., storybook, icons, docs)
- Description: Short summary of changes
- Body: Optional detailed description
- Breaking changes: Optional breaking change notes
- Issues: Optional issue references
Manual commits are also allowed but should follow this format:
git commit -m "type(scope): description"Examples:
feat(icons): add heart icon
fix(storybook): correct copy overlay behavior
docs(readme): update installation instructions
refactor(generate): simplify SVG optimizationGuidelines
- One icon per file
- Use semantic names (
chevron-down, noticon-1) - Test in Storybook before submitting
- Include icon source/license if applicable
- Use
npm run commitfor consistent commit messages
Troubleshooting
Icon not showing up?
Check the SVG file name
ls svgs/ # Should see your-icon.svgRebuild the library
npm run buildCheck TypeScript autocomplete
<FeralIcon name="your-icon" /> // ^ Should autocomplete
TypeScript errors?
If you see Type '"my-icon"' is not assignable to type 'IconName':
- Make sure you rebuilt:
npm run build - Restart your TypeScript server (VS Code:
Cmd+Shift+P→ "Restart TS Server") - Check that
src/iconRegistry.tsincludes your icon
Icons look wrong?
- Check SVG uses
fillnotstroke - Verify 19x19 viewBox
- Remove hardcoded colors (SVGO will strip fill attributes automatically)
Storybook not updating?
# Rebuild the library
npm run build
# Restart Storybook
npm run storybook