TL;DR: React Icons repackages roughly thirty icon families as React components behind one dependency. Import from the family's subpath so tree-shaking works, size and color follow CSS by default, and nothing loads a font file. Browser: react-icons.github.io/react-icons.
What is React Icons?
React Icons is a build of many popular icon sets into a single npm package, with each set behind its own import path. Font Awesome, Material Design, Bootstrap, Feather, Heroicons, Lucide, Simple Icons, Phosphor, Remix and others are all present, and each icon arrives as an ordinary React component rendering inline SVG.
The design goal is that adding an icon costs one import and roughly a kilobyte, rather than a font file, a sprite sheet, or a build step.
When to use it
Use it when a product needs icons from more than one family. That happens more often than it sounds: a general UI set for the interface, plus brand marks for third-party logins, plus a technical set for file types. Installing three packages and reconciling three prop APIs is worse than one.
Use something else when you know your family. If the design system says Lucide, lucide-react gives you the same icons with a smaller dependency graph and faster type checking, because your editor isn't indexing thirty sets.
Key Features
- Around thirty icon families behind one dependency
- Inline SVG components, so no font loading and no FOUT
1emsizing andcurrentColorfill, so icons follow surrounding textIconContext.Providerfor setting size, color and className across a subtree- Per-set import paths that let bundlers drop everything you don't use
- Types included, so every icon is a typed component
Installation
npm install react-icons
Import from the two-letter subpath of the set, never from the package root:
import { FaGithub } from 'react-icons/fa';
import { LuSettings } from 'react-icons/lu';
Using Icons
import { FiTrash2, FiEdit2 } from 'react-icons/fi';
export function RowActions({ onEdit, onDelete }: {
onEdit: () => void;
onDelete: () => void;
}) {
return (
<div className="row-actions">
<button onClick={onEdit} aria-label="Edit row">
<FiEdit2 />
</button>
<button onClick={onDelete} aria-label="Delete row">
<FiTrash2 />
</button>
</div>
);
}
Both buttons carry the accessible name. The icons stay silent, which is what you want when the control already says what it does.
Setting Defaults For a Subtree
import { IconContext } from 'react-icons';
export function Toolbar({ children }: { children: React.ReactNode }) {
return (
<IconContext.Provider value={{ size: '1.25rem', className: 'toolbar-icon' }}>
{children}
</IconContext.Provider>
);
}
This is the cleanest way to keep icon sizing consistent without repeating a prop on every instance.
TypeScript Tips
Every icon is an IconType, which is a function component taking IconBaseProps. That makes icons easy to pass around as data:
import type { IconType } from 'react-icons';
import { FiHome, FiUsers } from 'react-icons/fi';
type NavItem = { label: string; href: string; icon: IconType };
const nav: NavItem[] = [
{ label: 'Home', href: '/', icon: FiHome },
{ label: 'Team', href: '/team/', icon: FiUsers },
];
Render with <item.icon />. Typing the field as IconType rather than ReactNode keeps the component uninstantiated, so the consumer decides its size.
Common Gotchas
The big one is the barrel import. import { FaGithub } from 'react-icons' looks equivalent to the subpath version and is not; depending on your bundler it can pull far more than one icon. Always import from the set.
The second is editor performance. Thirty sets is a lot of type declarations, and autocomplete in a large project can get sluggish. If you have settled on one family, moving to that family's own package is the fix.
The third is duplicated concepts across sets. Nothing stops you using FaTrash in one component and FiTrash2 in another, and the mismatch is visible. Pick a primary set and treat the others as exceptions.