Heroicons
Recipe
Install @heroicons/react to use the official icon set from Tailwind Labs. Heroicons come in three variants: Outline (24px, 1.5px stroke), Solid (24px, filled), and Mini (20px, filled).
npm install @heroicons/react// app/components/heroicon-demo.tsx
"use client";
import { MagnifyingGlassIcon, Cog6ToothIcon } from "@heroicons/react/24/outline";
import { HomeIcon, BellIcon } from "@heroicons/react/24/solid";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
export function HeroiconDemo() {
return (
<div className="flex items-center gap-4">
<MagnifyingGlassIcon className="h-6 w-6 text-gray-500" />
<Cog6ToothIcon className="h-6 w-6 text-gray-500" />
<HomeIcon className="h-6 w-6 text-blue-600" />
<BellIcon className="h-6 w-6 text-yellow-500" />
<ChevronDownIcon className="h-5 w-5 text-gray-400" />
</div>
);
}Working Example
A navigation bar that uses outline icons for default states and solid icons for the active route:
// app/components/nav-bar.tsx
"use client";
import { usePathname } from "next/navigation";
import Link from "next/link";
import {
HomeIcon as HomeOutline,
MagnifyingGlassIcon as SearchOutline,
BellIcon as BellOutline,
UserIcon as UserOutline,
} from "@heroicons/react/24/outline";
import {
HomeIcon as HomeSolid,
MagnifyingGlassIcon as SearchSolid,
BellIcon as BellSolid,
UserIcon as UserSolid,
} from "@heroicons/react/24/solid";
interface NavLink {
href: string;
label: string;
outlineIcon: React.ElementType;
solidIcon: React.ElementType;
}
const navLinks: NavLink[] = [
{ href: "/", label: "Home", outlineIcon: HomeOutline, solidIcon: HomeSolid },
{ href: "/search", label: "Search", outlineIcon: SearchOutline, solidIcon: SearchSolid },
{ href: "/notifications", label: "Notifications", outlineIcon: BellOutline, solidIcon: BellSolid },
{ href: "/profile", label: "Profile", outlineIcon: UserOutline, solidIcon: UserSolid },
];
export function NavBar() {
const pathname = usePathname();
return (
<nav className="fixed bottom-0 left-0 right-0 border-t border-gray-200 bg-white">
<div className="mx-auto flex max-w-md items-center justify-around py-2">
{navLinks.map(({ href, label, outlineIcon: OutlineIcon, solidIcon: SolidIcon }) => {
const isActive = pathname === href;
const Icon = isActive ? SolidIcon : OutlineIcon;
return (
<Link
key={href}
href={href}
aria-label={label}
aria-current={isActive ? "page" : undefined}
className={`flex flex-col items-center gap-1 px-3 py-1 ${
isActive ? "text-blue-600" : "text-gray-500 hover:text-gray-700"
}`}
>
<Icon className="h-6 w-6" />
<span className="text-xs">{label}</span>
</Link>
);
})}
</div>
</nav>
);
}Deep Dive
How It Works
- Heroicons are organized into three import paths that correspond to the three variants:
@heroicons/react/24/outline-- 24x24 icons with 1.5px strokes, ideal for navigation and toolbars.@heroicons/react/24/solid-- 24x24 filled icons, great for active states and emphasis.@heroicons/react/20/solid-- 20x20 filled icons (Mini), designed for inline use with text, buttons, and form elements.
- Unlike Lucide, Heroicons do not have
sizeorcolorprops. You control dimensions and color entirely throughclassName(e.g.,h-6 w-6 text-blue-500). - This makes Heroicons the most Tailwind-native icon library. Every visual property is a utility class.
- Tree-shaking works per-icon because each icon is a named export from its variant path.
Variations
When to use each variant:
| Variant | Import Path | Size | Use Case |
|---|---|---|---|
| Outline | 24/outline | 24x24 | Navigation bars, toolbars, settings |
| Solid | 24/solid | 24x24 | Active states, primary actions, emphasis |
| Mini | 20/solid | 20x20 | Inline with text, inside buttons, form inputs |
Icon with text button:
import { PlusIcon } from "@heroicons/react/20/solid";
export function AddButton() {
return (
<button className="inline-flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700">
<PlusIcon className="h-5 w-5" />
Add Item
</button>
);
}Animated icon:
import { ArrowPathIcon } from "@heroicons/react/24/outline";
export function RefreshButton({ isLoading }: { isLoading: boolean }) {
return (
<button aria-label="Refresh" className="p-2">
<ArrowPathIcon
className={`h-5 w-5 text-gray-600 ${isLoading ? "animate-spin" : ""}`}
/>
</button>
);
}TypeScript Notes
- All Heroicon components are typed as
React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>. - They accept all standard SVG attributes plus
className,style, and ARIA attributes. - There is no dedicated
HeroIcontype export. UseReact.ComponentType<React.SVGProps<SVGSVGElement>>for typing icon props.
type HeroIcon = React.ComponentType<React.SVGProps<SVGSVGElement>>;
interface MenuItem {
icon: HeroIcon;
label: string;
}Gotchas
- Heroicons do not accept
sizeorcolorprops. Settingsize={24}will silently fail. UseclassName="h-6 w-6"instead. - The 24px outline and solid variants use the same component names (e.g.,
HomeIcon). You must alias one when importing both variants in the same file. - Mini icons are 20x20, not 16x16. If you need 16px icons, scale down with
h-4 w-4but be aware the visual weight may not be optimized for that size. - Heroicons v2 (the current version) has different icon names than v1. If migrating, check the icon name changes.
- Since Heroicons rely on
currentColor, make sure the parent element has the correct text color set.
Alternatives
| Approach | Pros | Cons |
|---|---|---|
| Heroicons | Perfect Tailwind integration, official Tailwind Labs | Smaller set (around 300 icons) |
| Lucide React | Larger icon set, size and color props | Requires additional prop configuration |
| React Icons (Hi2) | Access Heroicons alongside other families | Larger package, indirect dependency |
| Phosphor Icons | Multiple weights (thin, light, regular, bold, fill, duotone) | Not from Tailwind ecosystem |
FAQs
What are the three Heroicons variants and their import paths?
- Outline:
@heroicons/react/24/outline-- 24x24, 1.5px strokes, for navigation and toolbars. - Solid:
@heroicons/react/24/solid-- 24x24, filled, for active states and emphasis. - Mini:
@heroicons/react/20/solid-- 20x20, filled, for inline use with text and buttons.
How do you control the size and color of a Heroicon?
- Heroicons do not accept
sizeorcolorprops. - Use Tailwind classes:
className="h-6 w-6 text-blue-500". - This makes them the most Tailwind-native icon library.
Gotcha: What happens if you pass size={24} to a Heroicon component?
- It silently fails. The prop is not recognized.
- Heroicons only accept standard SVG attributes and
className. - Use
className="h-6 w-6"instead.
How do you use both outline and solid variants of the same icon in one file?
import { HomeIcon as HomeOutline } from "@heroicons/react/24/outline";
import { HomeIcon as HomeSolid } from "@heroicons/react/24/solid";You must alias one import since both variants share the same component name.
When should you use the Mini (20px) variant versus the Outline or Solid?
- Use Mini icons inline with text, inside buttons, and in form elements.
- Mini icons are 20x20 and optimized for smaller display contexts.
- Outline and Solid at 24x24 are better for navigation bars and toolbars.
How does the NavBar example toggle between outline and solid icons?
- It compares the current
pathname(fromusePathname()) to each link'shref. - If the route matches, it renders the Solid variant; otherwise the Outline variant.
aria-current="page"is set on the active link for accessibility.
How do you add a spin animation to a Heroicon?
<ArrowPathIcon
className={`h-5 w-5 ${isLoading ? "animate-spin" : ""}`}
/>Since styling is entirely via className, Tailwind animation utilities work directly.
What TypeScript type should you use for a Heroicon passed as a prop?
type HeroIcon = React.ComponentType<React.SVGProps<SVGSVGElement>>;
interface MenuItem {
icon: HeroIcon;
label: string;
}There is no dedicated HeroIcon type export from the package.
Gotcha: Are Heroicons v1 and v2 icon names compatible?
- No. Heroicons v2 has different icon names than v1.
- If migrating from v1, check the icon name changes in the v2.0.0 release notes.
Why do Heroicons rely on currentColor, and what can go wrong?
- All Heroicons use
currentColorfor their stroke or fill. - If the parent element has no explicit text color, the icon inherits whatever color is set higher in the DOM.
- Always set the correct text color on the parent or directly on the icon with a Tailwind class.
Can you scale Mini icons down to 16x16 safely?
- You can apply
h-4 w-4, but the visual weight may not be optimized for that size. - Mini icons are designed for 20x20. Scaling below that may look too heavy or lose detail.