Phosphor Icons: The Complete React Guide (6 Weights, Duotone & More)
Everything about @phosphor-icons/react — all 6 icon weights, the duotone system, SSR setup, IconContext for global defaults, and when to choose Phosphor over Lucide.
Phosphor Icons is the only major free icon library where every icon ships in six visual weights: Thin, Light, Regular, Bold, Fill, and Duotone. That single feature makes Phosphor uniquely suited to design systems that need to express hierarchy, state, or brand personality through icon weight — not just size and color.
This guide covers the full Phosphor API, the weight system in depth, the duotone color approach, and how to build a scalable Phosphor icon layer for React.
Why Phosphor over Lucide or Tabler
| Feature | Phosphor | Lucide | Tabler |
|---|---|---|---|
| Icons | 1,200+ | 1,500+ | 5,000+ |
| Weights/variants | 6 | 1 (outline) | 2 (line + filled subset) |
| Duotone support | Native | None | None |
| SSR-safe | Yes | Yes | Yes |
| MIT license | Yes | Yes | Yes |
Lucide wins on icon count. Tabler wins on coverage. Phosphor wins on flexibility per icon — if your design system uses weight to communicate hierarchy or importance, there is no better free option.
Installation
npm install @phosphor-icons/react
# or
pnpm add @phosphor-icons/react
Requires React 16.8+. Version 2.x (current) requires React 18+ for full concurrent mode support.
The six weight system
Every Phosphor icon is available in six weights from the same import path:
import {
Heart, // Regular (default)
HeartThin, // Thin
HeartLight, // Light
HeartBold, // Bold
HeartFill, // Fill
HeartDuotone, // Duotone
} from '@phosphor-icons/react';
Or use the weight prop on the base component:
import { Heart } from '@phosphor-icons/react';
// All six weights via prop
<Heart weight="thin" size={32} />
<Heart weight="light" size={32} />
<Heart weight="regular" size={32} /> // default
<Heart weight="bold" size={32} />
<Heart weight="fill" size={32} />
<Heart weight="duotone" size={32} />
Both approaches tree-shake identically. Use the named import (HeartFill) when the weight is fixed at compile time — it reads more clearly in JSX. Use the weight prop when weight is driven by component state (e.g., active/inactive toggle).
When to use each weight
| Weight | Visual | Use case |
|---|---|---|
| Thin | Hairline stroke | Editorial, luxury, whitespace-heavy UIs |
| Light | Fine stroke | Secondary actions, metadata, timestamps |
| Regular | Standard stroke | Body text complement, general UI |
| Bold | Heavy stroke | Primary actions, empty states, illustrations |
| Fill | Solid shape | Active states, selected items, CTAs |
| Duotone | Two-color | Featured items, illustrations, data viz |
The most powerful pattern: Regular for inactive, Fill for active. This is the most semantically legible active-state pattern available in any free icon library.
function TabItem({ icon, label, active }) {
const Icon = active ? `${icon}Fill` : icon; // simplified
// Better approach:
return (
<a className="flex flex-col items-center gap-1">
<PhosphorIcon
icon={icon}
weight={active ? 'fill' : 'regular'}
size={24}
className={active ? 'text-blue-600' : 'text-gray-500'}
/>
<span className="text-xs">{label}</span>
</a>
);
}
Duotone icons
Duotone weight renders two-layered icons — a primary element at full opacity and a secondary background element at 20% opacity by default.
Duotone Color Playground
Adjust two-tone colors and opacity before applying them to your icon set.
The secondary layer color is inherited from the icon’s color prop and rendered at opacity: 0.2. To use a different secondary color, wrap the icon and override the inner path directly:
// Default duotone — secondary is 20% of the primary color
<Heart weight="duotone" size={32} color="#2563eb" />
// Custom secondary via CSS
<span style={{ '--ph-duotone': '#93c5fd' } as React.CSSProperties}>
<Heart weight="duotone" size={32} color="#2563eb" />
</span>
Because duotone secondary opacity is relative to color (not a hardcoded value), switching color to a lighter shade in dark mode keeps the duotone relationship correct. Use CSS variables mapped to dark-mode values for consistent results.
Duotone for featured/highlighted UI
Duotone icons communicate “special” or “highlighted” states — useful for:
- Empty state illustrations (hero-style centered icons)
- Featured items in a grid
- Active/selected cards in a list
- Category badges with icon + label
<div className="flex flex-col items-center gap-4 p-8 text-center">
<FolderOpen weight="duotone" size={64} color="#2563eb" />
<h3 className="text-lg font-semibold">No files yet</h3>
<p className="text-sm text-muted-foreground">Upload your first file to get started.</p>
</div>
IconContext — global defaults
Like Tabler’s IconContext, Phosphor provides a context provider for global defaults:
import { IconContext } from '@phosphor-icons/react';
// Set global size, weight, color, and mirroring
export function App() {
return (
<IconContext.Provider value={{
weight: 'light',
size: 20,
color: 'currentColor',
mirroring: false,
}}>
<YourApp />
</IconContext.Provider>
);
}
Individual icon props override the context:
// Context: weight="light", size=20
<Gear /> // weight="light", size=20
<Gear weight="bold" /> // weight="bold", size=20
<Gear size={32} /> // weight="light", size=32
SSR and Server Components
Phosphor icons are SSR-safe — they render to static SVG elements with no client-side state.
// Next.js App Router — works in Server Components
// app/dashboard/page.tsx (no 'use client' needed)
import { ChartBar } from '@phosphor-icons/react';
export default function DashboardPage() {
return (
<h1 className="flex items-center gap-2">
<ChartBar weight="duotone" size={28} />
Analytics
</h1>
);
}
Building an icon system with Phosphor
For a design system, define your icon weight semantics once and reference them everywhere:
// src/lib/iconWeights.ts
export const iconWeights = {
default: 'regular',
inactive: 'light',
active: 'fill',
featured: 'duotone',
emphasis: 'bold',
} as const;
// src/components/Icon.tsx
import { iconWeights } from '@/lib/iconWeights';
import type { Icon as PhosphorIcon } from '@phosphor-icons/react';
interface IconProps {
icon: typeof PhosphorIcon;
variant?: keyof typeof iconWeights;
size?: number;
className?: string;
}
export function Icon({ icon: PhIcon, variant = 'default', size = 20, className }: IconProps) {
return (
<PhIcon
weight={iconWeights[variant]}
size={size}
className={className}
/>
);
}
Usage:
<Icon icon={Heart} variant="active" size={24} className="text-blue-600" />
<Icon icon={Star} variant="featured" size={32} className="text-amber-500" />
Phosphor vs Lucide: when to choose which
Choose Phosphor when:
- Your design system needs active/inactive state via fill vs outline
- You want duotone icons for illustrations or featured states
- You need visual weight variation (editorial, bold, minimal) across the same icon
- Your team uses Figma and wants weight mapped to Figma component properties
Choose Lucide when:
- You need more than 1,200 icons
- You want
createLucideIconfor fully custom icons that blend in - You’re already using shadcn/ui (Lucide is the default)
- You want the largest community and most StackOverflow answers
You don’t have to pick just one. Use Lucide as your base library for coverage, and add Phosphor for icons where duotone or weight variation add semantic value. The visual styles are close enough to coexist in the same UI.
Frequently asked questions
Does Phosphor have an icon search?
Yes — phosphoricons.com and AllSVGIcons (filter by ph: prefix). Both let you preview all 6 weights before picking.
How many unique icons are there?
About 1,200 base icons × 6 weights = ~7,200 total named exports. The package.json size is larger than Lucide’s but individual imports are comparable in size.
Do all 1,200 icons have all 6 weights? Yes — every icon in the library ships all six weights. This is a design requirement Phosphor enforces for every new icon.
Related Reading
- Best Free SVG Icon Libraries 2026 – The Definitive Comparison
- Duotone Icons in Light and Dark Themes
- Duotone SVG Icons: Color Guide
- SVG Icons in Dark Mode: The Complete Tailwind & CSS Guide
- SVG Icons for Navigation Menus
- Designing Icons for Color Blindness: Best Practices
- Phosphor Icons vs Lucide – The Two Best Modern React Icon Libraries