# How to Add Custom Icon Libraries Beyond Lucide to shadcn/ui Components

> Easily add custom icon libraries beyond Lucide to your shadcn/ui components. Learn how to configure your project and integrate any React icon set seamlessly.

- Repository: [shadcn-ui/ui](https://github.com/shadcn-ui/ui)
- Tags: how-to-guide
- Published: 2026-02-26

---

**Extend the `iconLibraries` map in [`packages/shadcn/src/icons/libraries.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/icons/libraries.ts), install the npm package, set the `iconLibrary` field in your [`components.json`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/icons/libraries.ts). Define the `name`, `title`, `packages` array, import template, usage pattern, and export path.

```typescript
// 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.

```bash
npm i @heroicons/react

# or pnpm add @heroicons/react

```

### 3. Configure Your Component

Update your [`components.json`](https://github.com/shadcn-ui/ui/blob/main/components.json) to specify the new library using the `iconLibrary` field. If omitted, **get-config.ts** defaults to `"lucide"`.

```json
{
  "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.

```tsx
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:

```tsx
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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/get-config.ts) file in [`packages/shadcn/src/utils/get-config.ts`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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:

```typescript
// 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:

```bash
pnpm add @heroicons/react

```

Configure the component:

```json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "components": [
    {
      "name": "alert",
      "iconLibrary": "heroicons",
      "files": ["app/components/ui/alert.tsx"]
    }
  ]
}

```

Use in your component:

```tsx
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 `iconLibraries` in [`packages/shadcn/src/icons/libraries.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/icons/libraries.ts) using `as const` for automatic type inference.
- **Install** the npm package in your project workspace.
- **Configure** the `iconLibrary` field in [`components.json`](https://github.com/shadcn-ui/ui/blob/main/components.json) to select your custom library.
- **Implement** icons using `IconPlaceholder` with library-specific props (e.g., `heroicons="IconName"`).
- **Transform** placeholders into concrete imports automatically via [`transform-icons.ts`](https://github.com/shadcn-ui/ui/blob/main/transform-icons.ts) during 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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/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`](https://github.com/shadcn-ui/ui/blob/main/components.json) configuration, enabling the same component code to work with Lucide, Heroicons, or any future library without source modifications.