How to Add Custom Icon Libraries Beyond Lucide to shadcn/ui Components
Extend the iconLibraries map in packages/shadcn/src/icons/libraries.ts, install the npm package, set the iconLibrary field in your components.json, and use library-specific props on the IconPlaceholder component to integrate any React icon set into shadcn/ui.
The shadcn/ui framework ships with a flexible icon system that defaults to Lucide but supports any React icon library exporting individual components. By registering new entries in the source registry and leveraging the IconPlaceholder abstraction, you can add custom icon libraries to shadcn components without modifying component internals. This approach uses type-safe configuration and build-time transformation to swap placeholder components for concrete icon imports.
How the Icon Library System Works
shadcn/ui centralizes icon management through a typed constant called iconLibraries located in packages/shadcn/src/icons/libraries.ts. This map defines metadata for each supported library, including package names, import patterns, and usage templates.
When you build or initialize components, the transform-icons.ts utility reads your components.json configuration, looks up the active library in iconLibraries, and replaces <IconPlaceholder /> instances with the correct import statements. Because iconLibraries uses as const assertion, TypeScript automatically infers valid library names, ensuring the Zod schema in apps/v4/registry/config.ts and runtime validation remain synchronized without manual type definition updates.
Step-by-Step Implementation
Follow these steps to register and use a custom icon library like Heroicons or Tabler Icons.
1. Register the Library in Source Code
Add a new entry to the iconLibraries object in packages/shadcn/src/icons/libraries.ts. Define the name, title, packages array, import template, usage pattern, and export path.
// packages/shadcn/src/icons/libraries.ts
export const iconLibraries = {
lucide: { /* existing */ },
// New library entry
heroicons: {
name: "heroicons",
title: "Heroicons",
packages: ["@heroicons/react"],
import: "import { ICON } from '@heroicons/react/24/outline'",
usage: "<ICON />",
export: "@heroicons/react",
},
} as const;
The as const assertion ensures TypeScript generates the IconLibraryName union type automatically, extending validation schemas immediately.
2. Install the NPM Package
Install the icon library in your consuming project or workspace root.
npm i @heroicons/react
# or pnpm add @heroicons/react
3. Configure Your Component
Update your components.json to specify the new library using the iconLibrary field. If omitted, get-config.ts defaults to "lucide".
{
"components": [
{
"name": "button",
"iconLibrary": "heroicons",
"files": ["./src/components/ui/button.tsx"]
}
]
}
4. Use the Icon Placeholder in JSX
Reference icons using the library-specific prop on the IconPlaceholder component. The prop name matches the library key defined in step 1.
import { IconPlaceholder } from "@/components/ui/icon-placeholder";
export function AddButton() {
return (
<button className="flex items-center gap-2">
<IconPlaceholder heroicons="AcademicCapIcon" className="size-4" />
Add Item
</button>
);
}
During the build process, transform-icons.ts replaces this placeholder with:
import { AcademicCapIcon as Icon } from "@heroicons/react/24/outline";
// ...
<Icon className="size-4" />
Technical Architecture Details
Build-Time Transformation
The transformation logic resides in packages/shadcn/src/utils/transformers/transform-icons.ts. This utility parses JSX AST, validates the iconLibrary value against the iconLibraries map, and generates concrete import statements. It also strips incompatible library-specific props to prevent runtime errors.
Configuration Loading
The get-config.ts file in packages/shadcn/src/utils/get-config.ts ensures every component configuration contains an iconLibrary field, injecting "lucide" as the default when unspecified. It validates the final value against the inferred IconLibraryName type.
Registry Validation
The apps/v4/registry/config.ts file defines a Zod schema that includes iconLibrary: z.enum([...]). Because the enum derives from iconLibraries, adding a new library in the source code automatically extends allowed values for registry validation.
Legacy Support (Optional)
For backward compatibility with older component definitions, map legacy library names in packages/shadcn/src/utils/legacy-icon-libraries.ts. This step is only required if you need to support existing components using deprecated library identifiers.
Complete Example: Adding Heroicons
Here is the end-to-end implementation for integrating Heroicons into a shadcn/ui project.
First, extend the registry:
// packages/shadcn/src/icons/libraries.ts
export const iconLibraries = {
lucide: {
name: "lucide",
title: "Lucide",
packages: ["lucide-react"],
import: "import { ICON } from 'lucide-react'",
usage: "<ICON />",
export: "lucide-react",
},
heroicons: {
name: "heroicons",
title: "Heroicons",
packages: ["@heroicons/react"],
import: "import { ICON } from '@heroicons/react/24/outline'",
usage: "<ICON />",
export: "@heroicons/react",
},
} as const;
Install the dependency:
pnpm add @heroicons/react
Configure the component:
{
"$schema": "https://ui.shadcn.com/schema.json",
"components": [
{
"name": "alert",
"iconLibrary": "heroicons",
"files": ["app/components/ui/alert.tsx"]
}
]
}
Use in your component:
import { IconPlaceholder } from "@/components/ui/icon-placeholder";
export function Alert() {
return (
<div className="rounded border p-4">
<IconPlaceholder heroicons="ExclamationTriangleIcon" className="size-4 text-amber-500" />
<span>Warning message</span>
</div>
);
}
Run your build command to execute the transformation.
Summary
- Register new libraries by adding entries to
iconLibrariesinpackages/shadcn/src/icons/libraries.tsusingas constfor automatic type inference. - Install the npm package in your project workspace.
- Configure the
iconLibraryfield incomponents.jsonto select your custom library. - Implement icons using
IconPlaceholderwith library-specific props (e.g.,heroicons="IconName"). - Transform placeholders into concrete imports automatically via
transform-icons.tsduring the build process.
Frequently Asked Questions
Do I need to manually update TypeScript types when adding a new icon library?
No. Because iconLibraries uses as const assertion in packages/shadcn/src/icons/libraries.ts, TypeScript automatically infers the IconLibraryName union type. This propagates to the Zod schema in apps/v4/registry/config.ts and runtime validation without manual type definition changes.
Can I use multiple icon libraries in the same project?
Yes, but on a per-component basis. Each component in components.json specifies its own iconLibrary. However, within a single component file, you should stick to one library per IconPlaceholder usage, as the transformer replaces all placeholders based on that component's configured library.
What happens if I don't specify an iconLibrary in components.json?
The get-config.ts utility automatically defaults the value to "lucide". Your component will use Lucide React icons unless you explicitly override the iconLibrary field in the configuration.
Why does shadcn/ui use IconPlaceholder instead of direct imports?
The IconPlaceholder abstraction decouples component source code from specific icon libraries. This allows the transform-icons.ts build-time transformer to swap in the correct imports based on your components.json configuration, enabling the same component code to work with Lucide, Heroicons, or any future library without source modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →