How to Use Shadcn Icons in Existing Library?

Written By Anand Patel 13 mins

How to Use Shadcn Icons in Existing Library?

A practical guide for React developers on integrating and customizing Shadcn icons in any existing project.

If you have been building React applications for a while, you know how much icons matter. They communicate actions, guide users, and give your UI a polished, intentional feel. But with so many icon libraries out there – Font Awesome, Heroicons, Phosphor, React Icons – the question becomes: which one plays nicely with your existing stack, especially if you’re using shadcn/ui?

This guide answers exactly that. We’ll explore how shadcn/ui handles icons and how to bring them into any existing project – the right way, with proper TypeScript types, accessibility in mind, and zero unnecessary bloat.

What are Shadcn Icons?

“Shadcn icons” isn’t a standalone icon package you install from npm. Instead, shadcn/ui ships with a flexible icon system that lets you choose your preferred Shadcn icon library with Lucide React as the default.

Supported Shadcn Icon Libraries

Icon LibraryPackagesStyle
Lucide (default)lucide-reactClean, minimal, outlined
Tabler Icons@tabler/icons-reactOutlined, pixel-perfect
HugeIcons@hugeicons/react, @hugeicons/core-free-iconsBold, modern, rounded
Phosphor Icons@phosphor-icons/reactMulti-weight, versatile
Remix Icon@remixicon/reactDual-tone, system-oriented

You can set your Shadcn icon library during project creation or by updating the iconLibrary field in your components.json. Once set, shadcn/ui uses that library consistently across all generated components.

Lucide React remains the default and most widely used choice. When you browse shadcn/ui component source code, you’ll typically see:

import { Search, AlertCircle, Loader2 } from "lucide-react";

So when people say “shadcn icons,” they generally mean Lucide React icons within the shadcn/ui ecosystem, but this guide covers how to work with any of the supported libraries.

Each Shadcn icon library installs on its own via npm/pnpm/yarn – you don’t need the full shadcn/ui setup to use them.

How shadcn Icons Work Under the Hood

Unlike traditional icon libraries that bundle fonts or sprite sheets, shadcn icons are plain React components that render inline SVGs. No magic, just React components all the way down.

The components.json Config

When you set up a shadcn/ui project, your components.json file includes an iconLibrary field:

{
  "$schema": "<https://ui.shadcn.com/schema.json>",
  "style": "new-york",
  "iconLibrary": "lucide",
  ...
}

This tells the shadcn CLI which icon package to use when generating components. When you run npx shadcn@latest add button, The CLI reads this config and wires in the correct icon imports automatically.

What Happens Inside a Component

Take the shadcn Alert component as an example. When iconLibrary is set to lucide, The generated code looks like:

import { AlertCircle } from "lucide-react";

export function AlertDemo() {
  return (
    <Alert>
      <AlertCircle className="h-4 w-4" />
      <AlertTitle>Error</AlertTitle>
      <AlertDescription>Something went wrong.</AlertDescription>
    </Alert>
  );
}

If you had chosen tabler Instead, the same component would generate:

import { IconAlertCircle } from "@tabler/icons-react";

export function AlertDemo() {
  return (
    <Alert>
      <IconAlertCircle className="h-4 w-4" />
      <AlertTitle>Error</AlertTitle>
      <AlertDescription>Something went wrong.</AlertDescription>
    </Alert>
  );
}

The component logic stays identical, only the icon import changes.

Why Inline SVGs?

Every icon renders as an inline <svg> in the DOM, which gives you:

  • Full CSS control – You style them with className Tailwind utilities, or CSS-in-JS, are just like any other element.
  • No network requests – No font files or sprite sheets to download. Icons are part of your JS bundle.
  • Tree-shakeable – You import only the icons you use. Unused icons are stripped out during build.
  • SSR-safe – Inline SVGs render on the server with no hydration issues.

Here’s what a Lucide icon actually renders in the DOM:

<svg
  xmlns="<http://www.w3.org/2000/svg>"
  width="24"
  height="24"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  class="lucide lucide-circle-alert"
  aria-hidden="true"
>
  <circle cx="12" cy="12" r="10"></circle>
  <line x1="12" x2="12" y1="8" y2="12"></line>
  <line x1="12" x2="12.01" y1="16" y2="16"></line>
</svg>

Notice stroke="currentColor" – This is why icons automatically inherit the text color of their parent. Set text-red-500 on a parent <div>, and the icon turns red. No extra props needed.

React components → inline SVGs → styled with Tailwind. No fonts, no sprites, no runtime overhead.

Installing and Using Shadcn Icons

Setup takes under a minute regardless of where you’re adding it.

Installation

Install the icon library of your choice. We’ll use Lucide (the default) as the primary example, but the pattern is the same for all supported libraries:

# Lucide (default)
pnpm add lucide-react

# Or pick one of the others:
pnpm add @tabler/icons-react
pnpm add @hugeicons/react @hugeicons/core-free-icons
pnpm add @phosphor-icons/react
pnpm add @remixicon/react

Already using shadcn/ui? The icon library is installed automatically based on your components.json config. You can skip this step.

Importing Icons

Each icon is a named export. Import only what you need:

import { Home, Settings, Bell } from "lucide-react";

The equivalent imports for other libraries:

// Tabler Icons
import { IconHome, IconSettings, IconBell } from "@tabler/icons-react";

// HugeIcons
import { HugeiconsIcon } from "@hugeicons/react";
import { Home07Icon, Setting07Icon, Notification01Icon } from "@hugeicons/core-free-icons";

// Phosphor Icons
import { HouseIcon, GearIcon, BellIcon } from "@phosphor-icons/react";

// Remix Icons
import { RiHome4Line, RiSettings2Line, RiNotification2Line } from "@remixicon/react";

Rendering Your First Icon

Lucide (and most libraries):

import { Home, Settings, Bell } from "lucide-react";

export function Navbar() {
  return (
    <nav className="flex items-center gap-4">
      <Home className="size-5" />
      <Settings className="size-5" />
      <Bell className="size-5" />
    </nav>
  );
}

No extra config, no provider wrappers, no CSS imports.

Hugeicons works differently. Instead of using the icon component directly, you pass it as a prop to a HugeiconsIcon wrapper:

import { HugeiconsIcon } from "hugeicons-react";
import { Home07Icon, Setting07Icon, Notification01Icon } from "hugeicons-react";

export function Navbar() {
  return (
    <nav className="flex items-center gap-4">
      <HugeiconsIcon icon={Home07Icon} strokeWidth={2} className="size-5" />
      <HugeiconsIcon icon={Setting07Icon} strokeWidth={2} className="size-5" />
      <HugeiconsIcon
        icon={Notification01Icon}
        strokeWidth={2}
        className="size-5"
      />
    </nav>
  );
}

Top Shadcn Icons Library

LibraryDirectoryIcons
Lucidelucide.dev/icons1,500+
Tablertabler.io/icons5,700+
Hugeiconshugeicons.com4,000+
Phosphorphosphoricons.com1,200+ (6 weights each)
Remixremixicon.com2,800+

Search by keyword, copy the component name, and import it directly.


Special Mention: Shadcn Studio

If you are working on a shadcn project, you can also use the best Shadcn UI Library: Shadcn Studio

This isn’t a traditional component library or a replacement for Shadcn. Instead, it’s a unique collection that offers customizable variants of components, blocks, and templates. Preview, customize, and copy-paste them into your apps with ease.

Building on the solid foundation of the Shadcn components & blocks, we’ve enhanced it with custom-designed components & blocks to give you a head start. This allows you to craft, customize, and ship your projects faster and more efficiently.

Features:

  • Open-source: Dive into a growing, community-driven collection of copy-and-paste 1000+ shadcn components and animated variants, Shadcn blocks, and templates.
  • Component & Blocks variants: Access a diverse collection of customizable 700+ shadcn blocks and component variants to quickly build and style your UI with ease.
  • Animated variants with Motion: Add smooth, modern animations to your components, enhancing user experiences with minimal effort.
  • Landing pages & Dashboards: Explore 20+ premium & free Shadcn templates for dashboards, landing pages & more. Fully customizable & easy to use.
  • shadcn/ui for Figma: Speed up your workflow with Shadcn Figma Design System, which consists of components, blocks & templates – a full design library inspired by shadcn/ui.
  • Powerful theme generator: Customize your UI instantly with Shadcn Theme Generator. Preview changes in real time and create consistent, on-brand designs faster.
  • shadcn/studio MCP: Integrate Shadcn MCP Server directly into your favorite IDE and craft stunning shadcn/ui Components, Blocks, and Pages inspired by shadcn/studio.
  • Shadcn Figma To Code Plugin: Convert your Figma designs into production-ready code instantly with the Shadcn Figma Plugin.

Customizing Icons

Since shadcn icons are inline SVGs, you style them with Tailwind – the same way you’d style any element. Size, color, stroke thickness, fill, transforms – it’s all just utility classes on the className prop.

The key thing: icons use stroke="currentColor" by default, so they automatically inherit the text color of their parent. That one detail eliminates most manual color overrides.

Size – use size- utility*

<Home className="size-4" />   {/* 16px */}
<Home className="size-6" />   {/* 24px - most common */}
<Home className="size-8" />   {/* 32px */}

Color – icons inherit text color via currentColor

<Home className="size-5 text-gray-500" />
<Home className="size-5 text-primary" />            {/* shadcn theme token */}
<Home className="size-5 text-muted-foreground" />   {/* shadcn theme token */}

Stroke width

<Home className="size-5 stroke-[1.5]" />   {/* thinner */}
<Home className="size-5 stroke-[2.5]" />   {/* thicker */}

Fill – useful for toggle states like favorites

<Heart className="size-5 fill-red-500 stroke-red-500" />

Transforms and animations

<ChevronDown className="size-4 rotate-180" />   {/* flip direction */}
<Loader2 className="size-5 animate-spin" />     {/* loading spinner */}
  • Parent color inheritance – Wrapping an icon in <button className="text-blue-600 hover:text-blue-800"> makes it follow the hover color automatically. No extra props.
  • Dark mode – Use shadcn theme tokens like text-foreground or text-muted-foreground and icons adapt automatically.
  • Transitionstransition-colors, transition-transform, hover:scale-110 all work directly on the icon.

Using Icons in shadcn/ui Components

shadcn/ui components don’t need special wrappers or icon slots – drop the icon in as a child and the component handles spacing. The consistent convention: className="size-4" on every icon.

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Mail, Plus, Loader2, Search, CircleAlert } from "lucide-react";

// Button with icon
<Button>
  <Mail className="size-4" />
  Send Email
</Button>

// Icon-only button
<Button variant="outline" size="icon">
  <Plus className="size-4" />
</Button>

// Loading state
<Button disabled>
  <Loader2 className="size-4 animate-spin" />
  Please wait
</Button>

// Search input with icon
<div className="relative">
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
  <Input className="pl-9" placeholder="Search..." />
</div>

// Alert with icon
<Alert variant="destructive">
  <CircleAlert className="size-4" />
  <AlertTitle>Error</AlertTitle>
  <AlertDescription>Something went wrong.</AlertDescription>
</Alert>

For nav or menu patterns, pass icons as components in your config – easy to read, easy to change later:

const navItems = [
  { icon: Home, label: "Dashboard", href: "/" },
  { icon: Users, label: "Users", href: "/users" },
  { icon: Settings, label: "Settings", href: "/settings" }
]

navItems.map((item) => (
  <a
    key={item.href}
    href={item.href}
    className="flex items-center gap-3 px-3 py-2 text-sm text-muted-foreground hover:text-foreground"
  >
    <item.icon className="size-4" />
    {item.label}
  </a>
))

Accessibility

Icons look great, but they can create problems for screen reader users if you’re not careful. The key question is: does this icon convey meaning, or is it purely decorative?

Decorative Icons

Most icons in a UI sit next to text that already describes the action – a trash can icon next to the word “Delete”, for example. These icons are decorative and should be hidden from screen readers:

// The text "Delete" already communicates the action
<Button>
  <Trash2 className="size-4" aria-hidden="true" />
  Delete
</Button>

Lucide sets aria-hidden="true" on the SVG automatically — unless you pass an accessibility prop like aria-label or aria-labelledby. So decorative icons are handled for you. The ones that need attention are icon-only interactive elements, covered below.

Meaningful Icons

When an icon is the only element communicating an action – like an icon-only button – you need to provide an accessible label:

// Icon-only button - needs an accessible name
<Button variant="outline" size="icon" aria-label="Delete item">
  <Trash2 className="size-4" />
</Button>

// Icon-only link
<a href="/settings" aria-label="Settings">
  <Settings className="size-4" />
</a>

Place the aria-label on the interactive parent (button, link), not on the icon itself. This ensures screen readers announce “Delete item, button” rather than just “button”.

Quick Rule of Thumb

  • Icon with visible text → decorative → add aria-hidden="true" to the icon
  • Icon without visible text → meaningful → add aria-label to the parent interactive element

Performance

The icon approach here is fast by default – tree shaking handles most of it automatically. But two mistakes are common enough to call out.

Tree Shaking

All five supported libraries are tree-shakable. When you write:

import { Home, Settings } from "lucide-react";

Only Home and Settings end up in your bundle – the other 1,500+ icons are stripped out during build. This works automatically with any modern bundler (Webpack, Vite, Turbopack).

Avoid importing the entire library:

// ❌ Bad - pulls in every icon
import * as Icons from "lucide-react";

// ✅ Good - only imports what you use
import { Home, Settings } from "lucide-react";

Bundle Size

Each icon is roughly 1–2 KB gzipped. Fifty icons is ~50–100 KB total – not worth worrying about.

If you’re above 100 icons, run @next/bundle-analyzer – You’ll usually find a handful that are imported but never actually rendered.

Avoid Creating Icons at Render Time

The other mistake: building an icon map inside the component body.

// ❌ Bad - creates a new component reference every render
function MyComponent({ iconName }: { iconName: string }) {
  const icons = { Home, Settings, Users };
  const Icon = icons[iconName]; // new reference each render
  
  return <Icon className="size-4" />;
}

// ✅ Good - stable reference via useMemo or defined outside render
const iconMap = { Home, Settings, Users } as const;

function MyComponent({ iconName }: { iconName: keyof typeof iconMap }) {
  const Icon = iconMap[iconName];
  
  return <Icon className="size-4" />;
}

The map defined outside the component keeps references stable across renders.

Conclusion

No icon fonts, no sprite sheets, no extra config. Pick a library, install it, import what you need – the workflow is the same regardless of which one you choose:

  1. Install the icon package
  2. Import only the icons you need
  3. Style them with Tailwind classes like any other element

These libraries work in any React project – you don’t need shadcn/ui installed, and they sit alongside whatever else you’re already using.

If you’re starting a new shadcn/ui project, set your preferred iconLibrary in components.json and the CLI handles the rest. If you’re adding icons to an existing project, just install the package and start importing.

Anand Patel

CPO & Co-Founder

Experience in digital product design. I lead the UI/UX design team with a focus on creating intuitive, high-performance user experiences. My approach combines strategic vision with a deep understanding of design and technology, turning ideas into impactful, scalable digital solutions.

Get notified about upcoming Premium & Free themes, Unique promo codes and Sales 🎉 !

[sibwp_form id=1]
] Announcements
Announcements Banner