Lucide vs Heroicons vs Phosphor Icons 2026
Lucide vs Heroicons vs Phosphor: React Icon Libraries in 2026
TL;DR
Lucide is the most actively maintained fork of Feather Icons — consistent design language, 1,500+ icons, excellent tree-shaking, and a clean React API. Heroicons is Tailwind CSS's official icon library — curated set of 292 polished icons in outline and solid variants, perfect if you're in the Tailwind ecosystem. Phosphor Icons has the most icons (7,700+) in the most weights (thin, light, regular, bold, fill, duotone) — unmatched variety, but at the cost of a larger surface area. For most Tailwind-first projects, Heroicons. For comprehensive icon needs, Phosphor. For the Feather-style aesthetic with active maintenance, Lucide.
Key Takeaways
- Phosphor React has 7,700+ icons in 6 weight variants — by far the largest collection
- Heroicons has the smallest bundle per icon — only 292 icons but each is highly optimized SVG
- Lucide React npm downloads: ~5M/week — the most popular standalone icon library for React
- All three are tree-shakeable — unused icons are excluded from your bundle
- Phosphor's 6 weights (thin, light, regular, bold, fill, duotone) enable visual hierarchy without multiple libraries
- Heroicons requires zero custom props for basic use — minimal API, works with Tailwind size classes
- Lucide allows stroke width customization — rare feature that significantly affects visual weight
Icon Libraries in 2026
With the rise of design systems and Figma-first workflows, icon libraries have become critical infrastructure. The key questions are:
- Does it match your design aesthetic?
- Does tree-shaking work correctly?
- How many icons are available?
- Can you customize stroke weight and size easily?
Lucide: The Feather Fork That Won
Lucide started as a fork of Feather Icons when Feather went unmaintained. It now has 1,500+ icons (vs Feather's 286), an active community, and consistent visual design.
Installation
npm install lucide-react
Basic Usage
import { Search, Bell, Settings, User, ChevronRight, Loader2 } from "lucide-react";
// Default size 24, stroke-width 2, color currentColor
function Navbar() {
return (
<nav className="flex items-center gap-4">
<Search size={20} />
<Bell size={20} strokeWidth={1.5} /> {/* Thinner stroke */}
<Settings size={20} className="text-gray-500" />
</nav>
);
}
Customization API
import { AlertCircle, CheckCircle, XCircle, Info } from "lucide-react";
// Lucide's unique feature: adjustable strokeWidth
function AlertBadge({ type }: { type: "error" | "success" | "warning" | "info" }) {
const iconMap = {
error: <XCircle size={16} strokeWidth={2.5} className="text-red-500" />,
success: <CheckCircle size={16} strokeWidth={2} className="text-green-500" />,
warning: <AlertCircle size={16} strokeWidth={1.5} className="text-yellow-500" />,
info: <Info size={16} strokeWidth={2} className="text-blue-500" />,
};
return iconMap[type];
}
// Custom default props — apply globally
import { createLucideIcon } from "lucide-react";
// All icons in your app use stroke-width 1.5 by default
const AppIcon = ({ Icon, ...props }: { Icon: React.ComponentType<any> }) => (
<Icon strokeWidth={1.5} {...props} />
);
Icon Creation (Custom Icons)
import { createLucideIcon } from "lucide-react";
// Create a custom icon with Lucide's API
const BrandLogo = createLucideIcon("BrandLogo", [
["circle", { cx: "12", cy: "12", r: "10" }],
["path", { d: "M8 12h8M12 8v8" }],
]);
// Use like any Lucide icon
<BrandLogo size={24} strokeWidth={2} className="text-blue-600" />
Dynamic Icons
// Load icons by name — useful for CMS-driven UIs
import { icons } from "lucide-react";
import type { LucideIcon } from "lucide-react";
interface IconProps {
name: keyof typeof icons;
size?: number;
className?: string;
}
export function DynamicIcon({ name, size = 24, className }: IconProps) {
const Icon: LucideIcon = icons[name];
if (!Icon) return null;
return <Icon size={size} className={className} />;
}
// Usage
<DynamicIcon name="Settings" size={20} className="text-gray-600" />
Tree-Shaking Verification
# Check bundle impact
npx bundlephobia lucide-react
# Full library: ~1.8 MB
# Per icon (tree-shaken): ~0.5 KB avg
# With Next.js, check in .next/static/chunks/
# Each used icon adds ~0.5 KB
Heroicons: Tailwind's Official Icons
Heroicons is designed and maintained by the Tailwind CSS team. It ships 292 icons in 3 variants: outline (24px), solid (24px), and mini (20px). The design is polished and integrates seamlessly with Tailwind size utilities.
Installation
npm install @heroicons/react
Basic Usage
// Three variants: outline, solid, mini
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon as MagnifyingGlassIconSolid } from "@heroicons/react/24/solid";
import { MagnifyingGlassIcon as MagnifyingGlassIconMini } from "@heroicons/react/20/solid";
function SearchBar() {
return (
<div className="relative">
<MagnifyingGlassIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
<input className="pl-10 pr-4 py-2 border rounded-lg" placeholder="Search..." />
</div>
);
}
Solid vs Outline
import { HeartIcon } from "@heroicons/react/24/outline";
import { HeartIcon as HeartIconSolid } from "@heroicons/react/24/solid";
function LikeButton({ liked, onToggle }: { liked: boolean; onToggle: () => void }) {
return (
<button
onClick={onToggle}
className="flex items-center gap-1 text-sm"
>
{liked ? (
<HeartIconSolid className="h-5 w-5 text-red-500" />
) : (
<HeartIcon className="h-5 w-5 text-gray-400 hover:text-red-400" />
)}
{liked ? "Liked" : "Like"}
</button>
);
}
With Tailwind Class Variants
import {
CheckCircleIcon,
ExclamationCircleIcon,
InformationCircleIcon,
XCircleIcon,
} from "@heroicons/react/24/solid";
type AlertType = "success" | "error" | "warning" | "info";
const alertConfig = {
success: { Icon: CheckCircleIcon, color: "text-green-500", bg: "bg-green-50" },
error: { Icon: XCircleIcon, color: "text-red-500", bg: "bg-red-50" },
warning: { Icon: ExclamationCircleIcon, color: "text-yellow-500", bg: "bg-yellow-50" },
info: { Icon: InformationCircleIcon, color: "text-blue-500", bg: "bg-blue-50" },
};
function Alert({ type, message }: { type: AlertType; message: string }) {
const { Icon, color, bg } = alertConfig[type];
return (
<div className={`flex items-center gap-3 p-4 rounded-lg ${bg}`}>
<Icon className={`h-5 w-5 shrink-0 ${color}`} />
<p className="text-sm">{message}</p>
</div>
);
}
Bundle Impact
# Heroicons is organized into separate import paths
# Each variant is a separate package chunk
# @heroicons/react/24/outline → ~150 KB (full set)
# Per icon: ~0.4 KB tree-shaken
# Tree-shaking works automatically with named imports
import { ArrowRightIcon } from "@heroicons/react/24/outline"; // Only this icon
Phosphor Icons: Maximum Variety
Phosphor has 7,700+ icons across 6 weight variants. If you need a specific icon for a niche use case, Phosphor almost certainly has it.
Installation
npm install @phosphor-icons/react
Six Weights
import {
Heart, // Regular (default)
Heart as HeartThin, // Override weight prop
Heart as HeartBold, // Override weight prop
} from "@phosphor-icons/react";
function WeightDemo() {
return (
<div className="flex gap-4 items-center">
<Heart size={32} weight="thin" /> {/* Thinnest */}
<Heart size={32} weight="light" />
<Heart size={32} weight="regular" /> {/* Default */}
<Heart size={32} weight="bold" />
<Heart size={32} weight="fill" /> {/* Filled */}
<Heart size={32} weight="duotone" /> {/* Two-tone */}
</div>
);
}
Duotone Icons (Unique Feature)
import { CloudArrowUp, Database, Shield } from "@phosphor-icons/react";
// Duotone: body is 30% opacity, accent at full opacity
function FeatureCard({ title, description }: { title: string; description: string }) {
return (
<div className="p-6 bg-white rounded-xl border">
<CloudArrowUp size={48} weight="duotone" className="text-blue-600 mb-4" />
<h3 className="font-semibold">{title}</h3>
<p className="text-gray-500 text-sm mt-1">{description}</p>
</div>
);
}
Global Context (Set Defaults)
import { IconContext } from "@phosphor-icons/react";
// Set default props for all icons in a subtree
function App() {
return (
<IconContext.Provider value={{ size: 20, weight: "bold", mirrored: false }}>
<main>
{/* All icons here use size=20, weight=bold */}
<Navigation />
<Content />
</main>
</IconContext.Provider>
);
}
Bundle Size Considerations
# Phosphor is large — but tree-shaking handles it
npx bundlephobia @phosphor-icons/react
# Full library: ~14 MB
# Per icon (tree-shaken): ~1 KB avg (slightly larger than Lucide due to variants)
# With duotone icons, each icon has two SVG paths
# Recommendation: use consistent weight across your app to minimize variants loaded
Feature Comparison
| Feature | Lucide | Heroicons | Phosphor |
|---|---|---|---|
| Icons count | 1,500+ | 292 | 7,700+ |
| Style variants | Outline only | Outline + Solid + Mini | 6 weights |
| Duotone icons | ❌ | ❌ | ✅ |
| Stroke width control | ✅ (unique) | ❌ | ❌ |
| Tree-shakeable | ✅ | ✅ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| Per-icon size (tree-shaken) | ~0.5 KB | ~0.4 KB | ~1 KB |
| Dynamic loading | ✅ | ❌ | ✅ |
| Context/default props | ❌ | ❌ | ✅ |
| npm downloads/week | ~5M | ~2M | ~700k |
| Tailwind ecosystem fit | ✅ | ✅ Native | ✅ |
| Design aesthetic | Minimal/clean | Polished/Tailwind | Versatile |
| Custom icon API | ✅ | ❌ | ❌ |
Bundle Size Reality Check
Scenario: Use 50 icons in a production app
Lucide: 50 × 0.5 KB = 25 KB (gzipped: ~8 KB)
Heroicons: 50 × 0.4 KB = 20 KB (gzipped: ~7 KB)
Phosphor: 50 × 1 KB = 50 KB (gzipped: ~15 KB)
All three are negligible in the context of a real app.
The bundle size difference doesn't matter in practice.
The decision should be based on aesthetics and feature needs.
When to Use Each
Choose Lucide if:
- You want the Feather Icons aesthetic (clean, minimal, stroke-based)
- You need
strokeWidthcustomization for visual hierarchy - You need a custom icon that matches the Lucide style via
createLucideIcon - Your app has a variety of icon sizes and you need consistent weight control
Choose Heroicons if:
- You're using Tailwind CSS and want icons from the same design team
- Your icon needs are modest — you need 50–100 common UI icons
- You want solid/outline variants for interactive states (like/unlike, bookmark)
- Zero dependencies and minimal API surface matters
Choose Phosphor if:
- You need niche or domain-specific icons not found in smaller libraries
- Duotone icons fit your design system (marketing pages, feature callouts)
- You need multiple weights (thin for decorative, bold for interactive) across your app
- You're building a design system that needs icon weight as a design token
Ecosystem and Community Health
Lucide has the most active development community of the three. The GitHub repository accepts contributions for new icons through a structured proposal process — designers submit SVG files that are reviewed against the Lucide style guide (consistent stroke width, optical sizing, rounded linecaps). This quality filter means new icons maintain the library's visual cohesion, unlike some icon sets that feel inconsistent because contributors have varying design skill. The npm downloads of 5M per week place Lucide in a category with widely-adopted infrastructure packages.
The relationship with Feather Icons is worth understanding. Feather's original creator largely stopped maintaining the library around 2021, and Lucide emerged as the community-driven continuation. Lucide's icon count grew from Feather's 286 to over 1,500 icons over three years of community contributions. Importantly, Lucide is backward compatible with Feather — every Feather icon exists in Lucide under the same or similar name.
Heroicons is maintained directly by the Tailwind Labs team — the same team that builds Tailwind CSS, Headless UI, and Catalyst (Tailwind's component library). This direct alignment means Heroicons updates ship alongside major Tailwind CSS releases, and the design language of Heroicons is the official visual vocabulary of the Tailwind ecosystem. For teams where design consistency across the Tailwind component suite matters, Heroicons is the natural choice.
Phosphor Icons is a more ambitious project — a single icon family with six distinct weights that can serve as a complete design system's icon vocabulary. The 700k weekly downloads are lower than Lucide and Heroicons, but Phosphor attracts projects where icon variety and weight control are genuine requirements. The design team behind Phosphor runs a commercial design business, and the icons reflect that professional quality.
Real-World Adoption
Lucide has become the default icon library for the shadcn/ui ecosystem. When developers run npx shadcn add button, the generated button component imports Lucide icons internally. The shadcn CLI itself uses Lucide icons in its UI. This deep integration with the most popular React component library ecosystem drives a significant portion of Lucide's downloads. For the animated component libraries that work alongside these icon sets, see Aceternity UI vs Magic UI vs shadcn animated React components 2026.
Linear, the project management tool, uses an icon set stylistically similar to Lucide — the clean, minimal stroke aesthetic has become the visual signature of modern developer tools. Teams building dashboards, analytics products, and developer-facing UIs gravitate toward this aesthetic, making Lucide the natural icon choice.
Heroicons appears in production across the entire Tailwind ecosystem. Any project scaffolded with Tailwind's starter templates or Tailwind UI component library (the paid kit) likely uses Heroicons. The 292-icon constraint is rarely a limitation for teams using Tailwind UI components because those components define the icon usage patterns, and the 292 icons cover all those patterns.
Phosphor Icons is particularly popular in design systems for consumer applications — mobile apps, marketing websites, and products where visual richness matters. The duotone weight is almost exclusively used for feature illustration and onboarding screens, not for functional UI icons. Consumer fintech apps, fitness apps, and travel products have adopted Phosphor's duotone icons for their visual warmth compared to the utilitarian look of Lucide or Heroicons. For testing icon rendering across variants, see best JavaScript testing frameworks 2026.
Developer Experience Deep Dive
Lucide's TypeScript experience is first-class. Every icon is a React component with the LucideIcon type, which accepts size, color, strokeWidth, and all standard SVG props. The icons object export lets you build a dynamic icon registry indexed by name — useful for CMS-driven UIs where icon names come from a database field. The createLucideIcon function lets you define custom icons that follow the same API, which is handy for brand icons and custom UI glyphs that need to match the Lucide aesthetic.
One Lucide feature competitors don't offer is strokeWidth customization. Because Lucide icons are SVG paths with stroke attributes, you can adjust the visual weight of any icon. strokeWidth={1.5} gives a lighter, more editorial feel. strokeWidth={2.5} gives bolder icons appropriate for primary actions. This single prop replaces the need for multiple icon variants (outline vs solid) for most use cases.
Heroicons' organization into separate import paths (@heroicons/react/24/outline, @heroicons/react/24/solid, @heroicons/react/20/solid) is a design decision worth understanding. The three variants exist for different visual contexts: 24px outline for standard UI icons, 24px solid for filled/active states (like a filled heart for a liked item), and 20px mini for compact interfaces like table rows and pill buttons. Using the correct variant in context — outline when inactive, solid when active — creates visual feedback without color changes.
Phosphor's weight prop approach is elegant for a single-component API that provides six visual variants. The IconContext.Provider pattern for setting defaults globally is useful for enforcing consistent weight across a component library. The gotcha is that Phosphor's bundle size per icon is larger than Lucide or Heroicons because each icon must support all six weights. If you use only the regular weight throughout your app, you're loading SVG path data for the other five weights unnecessarily. Phosphor's tree-shaking is at the icon level, not the weight level.
Figma and Design Workflow Integration
All three libraries have official Figma plugins or community-maintained resources, but the workflow differs. Lucide's Figma plugin lets designers search and insert icons that are pixel-perfect matches to the React components developers use. Because the icon geometry is identical between Figma and the React component, icon names and sizes in Figma designs map directly to code with no visual discrepancy.
Heroicons provides a Figma component library that includes all three variants (outline, solid, mini) as named components. The naming convention matches the React import names exactly — ArrowRightIcon in Figma equals ArrowRightIcon in @heroicons/react/24/outline. Design handoff from Figma to code requires zero translation work for icons.
Phosphor's Figma resources include the full 7,700+ icon set with all six weights as component variants. Designers can adjust weight in Figma using component properties, and developers implement the same adjustment via the weight prop. For design systems where icons have intentional weight variations across different UI contexts, this parity is valuable.
Final Verdict 2026
For Tailwind CSS + shadcn/ui projects, Lucide is the pragmatic default. It's already in your dependency tree via shadcn/ui, the aesthetic matches Tailwind's design philosophy, and the strokeWidth customization handles both light and bold contexts without importing a second icon set.
For Tailwind UI and Headless UI projects, Heroicons is the obvious choice. Using any other icon library means maintaining visual consistency with the Tailwind UI components that ship with Heroicons icons embedded in their designs.
For design systems and consumer applications that need icon variety, weighted variants, and duotone styles for marketing and illustration contexts, Phosphor is the most capable library. The investment in a slightly larger bundle size is worth it for products where icon polish is a differentiator.
In practice, many professional codebases use Lucide for application UI icons and add Phosphor for marketing pages or onboarding illustrations — the same pattern of mixing libraries seen with Aceternity UI and shadcn/ui.
Methodology
Data sourced from npm download statistics (npmjs.com, January 2026), GitHub repositories, official documentation, and bundle size measurements from Bundlephobia. Icon counts verified from official GitHub repositories as of February 2026. npm weekly downloads: Lucide React (5M), Heroicons (2M), Phosphor React (700k).
Related: Best React Component Libraries 2026, Aceternity UI vs Magic UI vs shadcn/ui 2026, Best JavaScript Charting Libraries 2026