@brightlocal/icons
v2.0.0
Published
BrightLocal Design System Icons - Lucide defaults and custom icons
Keywords
Readme
@brightlocal/icons
BrightLocal Design System Icons - Lucide defaults and custom icons
Overview
This package provides a unified icon system for the BrightLocal Design System, combining:
- All 5000+ Lucide React icons (tree-shakeable)
- Custom BrightLocal icons
- Dynamic icon loading for database-driven UI
- Full TypeScript support
Installation
pnpm add @brightlocal/iconsUsage
1. Direct Import (Recommended)
Best for tree-shaking - only imports the icons you use.
import { Check, Heart, Star } from "@brightlocal/icons";
<Check className="w-4 h-4" />
<Heart size={20} color="red" fill="red" />
<Star size={16} strokeWidth={2} />2. DynamicIcon Component
For database-driven icon rendering with code-splitting. Icons are loaded dynamically only when used.
import { DynamicIcon } from "@brightlocal/icons";
// From database or CMS
const iconName = user.settings.preferredIcon; // "heart"
<DynamicIcon name={iconName} size={20} />
// Works with custom icons too
<DynamicIcon name="bright-local-logo" size={32} />
// With Suspense fallback (optional)
<React.Suspense fallback={<div className="w-6 h-6 animate-pulse bg-gray-200 rounded" />}>
<DynamicIcon name={dbIconName} size={16} />
</React.Suspense>3. Custom BrightLocal Icons
import { BrightLocalLogo } from "@brightlocal/icons";
<BrightLocalLogo className="h-8 w-8 text-blue-600" />;Component Props
DynamicIcon Component
name: Icon name (kebab-case, e.g., "camera", "chevron-down", "bright-local-logo")size: Icon size in pixels (default: 16)color: Icon color (uses currentColor by default)strokeWidth: Stroke width (default: 1.33)- All other SVG props
Flag & Social Media Icons
size: Icon size in pixels (default: 16)className: CSS classes for styling- All standard SVG props
Benefits
- Tree-shakeable: Only imports icons you use (with direct imports)
- Type-safe: Full TypeScript support with autocomplete
- Flexible: Multiple import patterns for different use cases
- Lazy loading: DynamicIcon splits icons into separate chunks
- Consistent: Unified API across all icons
- Optimized: Minimal bundle impact
Adding Custom Icons
Method 1: Using createLucideIcon (Recommended)
Compatible with DynamicIcon and follows Lucide's guidelines (16×16 canvas, 2px strokes).
// src/custom-icons/my-icon.tsx
import { createLucideIcon } from "@brightlocal/icons";
export const MyIcon = createLucideIcon("MyIcon", [
["path", { d: "M12 2v20M2 12h20", key: "cross" }],
["circle", { cx: "12", cy: "12", r: "3", key: "center" }],
]);
export default MyIcon;Then add to dynamic imports:
// src/icons/dynamic-icon-imports.ts
const customIconImports = {
"my-icon": () =>
import("../custom-icons/my-icon.js").then((m) => ({
default: m.MyIcon,
})),
};Method 2: Plain SVG Component
For icons that don't need dynamic loading.
// src/custom-icons/my-icon.tsx
import * as React from "react";
export const MyIcon = (props: React.SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 16 16" fill="none" {...props}>
<path d="M12 2v20M2 12h20" stroke="currentColor" strokeWidth={2} />
</svg>
);Examples
Icon Gallery
import { Check, X, Plus, Minus, Heart, Star } from "@brightlocal/icons";
const icons = [Check, X, Plus, Minus, Heart, Star];
<div className="grid grid-cols-6 gap-4">
{icons.map((Icon, i) => (
<Icon key={i} size={16} />
))}
</div>;Database-Driven Icons
import { DynamicIcon } from "@brightlocal/icons";
// Icons stored in database
const features = [
{ id: 1, name: "Fast", icon: "zap" },
{ id: 2, name: "Secure", icon: "shield" },
{ id: 3, name: "Scalable", icon: "trending-up" },
];
<div className="grid grid-cols-3 gap-4">
{features.map((feature) => (
<div key={feature.id}>
<DynamicIcon name={feature.icon} size={32} />
<h3>{feature.name}</h3>
</div>
))}
</div>;Loading States
import { Loader2 } from "@brightlocal/icons";
<button disabled>
<Loader2 className="mr-2 animate-spin" size={16} />
Loading...
</button>;Icon Naming Convention
When using DynamicIcon, icon names follow kebab-case format:
- Lucide icons: Convert PascalCase to kebab-case
ChevronDown→"chevron-down"AlertCircle→"alert-circle"UserPlus→"user-plus"
- Custom icons: Use kebab-case consistently
BrightLocalLogo→"bright-local-logo"
Performance Considerations
Direct Imports vs DynamicIcon
Use Direct Imports when:
- Icons are known at build time
- Maximum tree-shaking is desired
- You want the smallest possible bundle
import { Camera, Heart } from "@brightlocal/icons";Use DynamicIcon when:
- Icon names come from database/API
- You need code-splitting for large icon sets
- Building configurable UI components
<DynamicIcon name={iconFromDatabase} />Bundle Size Impact
- Direct import: ~1-2KB per icon (tree-shaken)
- DynamicIcon: ~4KB + icon loaded on demand
Default Icon Properties
All Lucide icons from @brightlocal/icons are wrapped with design system defaults:
| Property | Default | Description |
|----------|---------|-------------|
| size | 16 | Icon dimensions in pixels |
| strokeWidth | 1.33 | Matches Figma design specs |
| absoluteStrokeWidth | true | Keeps stroke consistent regardless of icon size |
Default Sizes by Icon Type
| Icon Type | Default Size | Use Case | |-----------|--------------|----------| | Lucide icons | 16px | UI elements, buttons, inputs | | DynamicIcon | 16px | Database-driven icons | | Flag icons | 16px | Country/region indicators | | Social Media icons | 16px | Brand logos, social links |
// Lucide icons default to 16px with 1.33 strokeWidth
<Check />
// Explicit size — stroke stays consistent (absoluteStrokeWidth: true)
<Check size={20} />
<Check size={12} />TypeScript Support
Full TypeScript support with type inference:
import { DynamicIcon } from "@brightlocal/icons";
import type { DynamicIconProps } from "@brightlocal/icons";
// Props are fully typed
const props: DynamicIconProps = {
name: "heart",
size: 16,
color: "#ff0000",
};Accessibility
Icons should include proper accessibility attributes:
// Decorative icons (no semantic meaning)
<Check aria-hidden="true" />
// Meaningful icons (provide label)
<Heart aria-label="Like this post" role="img" />
// Interactive icons (use with button/link)
<button aria-label="Close dialog">
<X size={20} />
</button>Troubleshooting
Icon not displaying
- Check the icon name is correct (case-sensitive for direct imports)
- Verify the icon exists in Lucide library
- For DynamicIcon, ensure the icon is registered in
dynamic-icon-imports.ts
TypeScript errors
Make sure you have the latest types installed:
pnpm add -D @types/reactBundle size too large
Use direct imports instead of importing everything:
// ❌ Don't do this
import * as Icons from "@brightlocal/icons";
// ✅ Do this
import { Camera, Heart, Star } from "@brightlocal/icons";Migration from Other Icon Libraries
From React Icons
// Before (react-icons)
import { FaHeart } from "react-icons/fa";
<FaHeart size={16} />;
// After (brightlocal/icons)
import { Heart } from "@brightlocal/icons";
<Heart size={16} />;From Heroicons
// Before (heroicons)
import { HeartIcon } from "@heroicons/react/16/outline";
<HeartIcon className="h-6 w-6" />;
// After (brightlocal/icons)
import { Heart } from "@brightlocal/icons";
<Heart className="h-6 w-6" />;Resources
- Lucide Icon Gallery - Browse all 5000+ available icons
- Lucide Documentation - Learn more about Lucide icons
- Storybook Documentation - Interactive icon gallery
- Package Source - View source code
Contributing
To add new custom icons:
- Create icon file in
src/custom-icons/ - Export from
src/index.ts - Add to dynamic imports in
src/icons/dynamic-icon-imports.ts - Update
customIconsDatain Storybook documentation - Run build:
pnpm build
License
Part of the BrightLocal Design System. See main repository for license details.
