# How to Create Custom Components that Integrate with shadcn's Registry System

> Learn how to create custom components that integrate seamlessly with shadcn's registry system. Follow simple steps to declare and build your own components.

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

---

**You can create custom components that integrate with shadcn's registry system by placing your component file in the appropriate registry folder, declaring it in a style-specific [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) file, and letting the build script generate the runtime index that powers lazy loading and dependency resolution.**

The shadcn/ui ecosystem uses a **registry-based architecture** to manage UI components as declarative items. When you create custom components that integrate with shadcn's registry system, you tap into automatic dependency tracking, lazy loading, and style-specific resolution without modifying core library code. This guide walks through the exact file paths, schema requirements, and runtime APIs used in the `shadcn-ui/ui` repository.

## Understanding the Registry Architecture

shadcn/ui exposes every UI building block—components, hooks, blocks, and pages—as a declarative registry item. At build time, a script scans the registry folders (e.g., `apps/v4/registry/...`) and generates a map (`Index`) that the runtime library consumes to load components lazily, resolve file paths, and cache results.

| Concept | Description | Location |
|---------|-------------|----------|
| **Registry item definition** | JSON-like object describing name, type, dependencies, and source files | `apps/v4/registry/<style>/ui/_registry.ts` |
| **Index generation** | Auto-generated `Index` mapping style to item name to `RegistryItem` | [`apps/v4/registry/__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx) (generated by `scripts/build-registry.mts`) |
| **Runtime helpers** | Functions like `getRegistryComponent` that read the index, load files, fix imports, and cache results | [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) |
| **LRU cache** | In-memory cache to avoid repeated file I/O during development | [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) (lines 14-20) |
| **File-path fixing** | Normalizes paths and target locations for generated files | [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) (`fixFilePaths`, `getFileTarget`) |
| **Demo integration** | Optional demo component lookup that prefers examples when available | [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) (`getDemoComponent`, `getDemoItem`) |

## Step-by-Step Workflow to Create Custom Registry Components

When you add a new component, you only need to declare it in a registry file and place the implementation file in the matching folder. The build script picks it up automatically, and the runtime helpers expose it via `getRegistryComponent` or a direct import from the generated `Index`.

### Step 1: Create the Component File

Place your component in the appropriate style folder. For the `new-york-v4` style, this is `apps/v4/registry/new-york-v4/ui/`.

```tsx
// apps/v4/registry/new-york-v4/ui/my-button.tsx
import * as React from "react"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/registry/new-york-v4/ui/button"

export interface MyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  /** Optional visual variant */
  variant?: keyof typeof buttonVariants
}

/**
 * A thin wrapper around the default `button` component that demonstrates
 * how to add a custom UI element to the registry.
 */
export const MyButton = React.forwardRef<HTMLButtonElement, MyButtonProps>(
  ({ className, variant = "default", ...props }, ref) => (
    <button
      ref={ref}
      className={cn(buttonVariants({ variant }), className)}
      {...props}
    />
  )
)
MyButton.displayName = "MyButton"

```

The component lives next to other UI items so the path remains consistent with the registry structure.

### Step 2: Register the Component in the Style-Specific Registry

Add an entry to the [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) file for your target style. This file exports an array of `Registry["items"]` that the build script consumes.

```ts
// apps/v4/registry/new-york-v4/ui/_registry.ts
import { type Registry } from "shadcn/schema"

export const ui: Registry["items"] = [
  // … existing items
  {
    name: "my-button",
    type: "registry:ui",
    // optional list of external packages this component needs
    dependencies: [],

    // The file(s) that make up the component.
    files: [
      {
        path: "ui/my-button.tsx",
        type: "registry:ui",
      },
    ],
  },
]

```

The structure matches every other entry in the file. The schema is validated against `registryItemSchema` from [`packages/shadcn/schema/index.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/schema/index.ts).

### Step 3: Regenerate the Registry Index

Run the build script to scan all [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) files and regenerate the runtime index.

```bash

# In a development environment the watch mode updates automatically.

# To run it manually:

pnpm run build-registry   # ← uses apps/v4/scripts/build-registry.mts

```

The script writes to [`apps/v4/registry/__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx), creating a map that associates style names with lazy-loaded component factories.

### Step 4: Consume the Component in Your Application

Use the runtime helper `getRegistryComponent` to lazily load your custom component.

```tsx
// app/example/page.tsx
import * as React from "react"
import { getRegistryComponent } from "@/lib/registry"

export default async function ExamplePage() {
  // Lazily load the component from the registry
  const MyButton = await getRegistryComponent("my-button", "new-york-v4")

  return (
    <div className="p-8">
      <h1 className="mb-4 text-2xl font-bold">Custom Registry Component</h1>

      {/* The returned value is a React component */}
      <MyButton variant="outline">Click me</MyButton>
    </div>
  )
}

```

Internally, `getRegistryComponent` (lines 75-84 in [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts)) checks for a demo component first, then falls back to the generated `Index`:

```ts
export function getRegistryComponent(name: string, styleName: string) {
  const demoComponent = getDemoComponent(name, styleName)
  if (demoComponent) return demoComponent

  const { index, key } = getIndexForStyle(styleName)
  return index[key]?.[name]?.component
}

```

## Accessing Raw Registry Definitions for Code Generation

If you need to inspect the component's source files or metadata—for example, to build a code-generation tool—use `getRegistryItem` instead of the component loader.

```ts
import { getRegistryItem } from "@/lib/registry"

async function inspect() {
  const item = await getRegistryItem("my-button", "new-york-v4")
  console.log(item?.files) // → [{ path: ".../ui/my-button.tsx", content: "...", ... }]
}

```

This function (lines 85-115 in [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts)) reads the source file, fixes imports using the internal `fixImport` utility, and returns a fully-typed object validated against `registryItemSchema`.

## Key Files in the Registry System

| File | Role | Link |
|------|------|------|
| [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) | Runtime helpers (`getRegistryComponent`, caching, import fixing) | [registry.ts](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) |
| [`apps/v4/registry/new-york-v4/ui/_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/_registry.ts) | Style-specific UI item declarations – where you add your component entry | [_registry.ts](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/_registry.ts) |
| [`apps/v4/registry/__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx) | Auto-generated map consumed by the runtime; created by the build script | [__index__.tsx](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx) |
| `apps/v4/scripts/build-registry.mts` | Scans all [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) files and writes [`__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/__index__.tsx) | [build-registry.mts](https://github.com/shadcn-ui/ui/blob/main/apps/v4/scripts/build-registry.mts) |
| [`packages/shadcn/schema/index.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/schema/index.ts) | Zod schema for registry items (`registryItemSchema`) used for validation | [schema/index.ts](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/schema/index.ts) |

These files together constitute the **registry system**. By placing a new component file in the appropriate folder and adding a matching entry to the corresponding [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts), you seamlessly integrate custom UI pieces into shadcn’s ecosystem.

## Summary

- **Registry-based architecture**: shadcn/ui uses declarative registry items defined in [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) files and a generated [`__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/__index__.tsx) for runtime resolution.
- **File placement**: Store component source files in `apps/v4/registry/<style>/ui/` and register them in the style-specific [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) using the `registryItemSchema` structure.
- **Build process**: Run `pnpm run build-registry` (or rely on watch mode) to regenerate [`apps/v4/registry/__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx), which maps names to lazy-loaded components.
- **Runtime consumption**: Use `getRegistryComponent(name, style)` from [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) to lazily load components, or `getRegistryItem` to access raw file content and metadata for code generation.
- **Caching and path fixing**: The runtime uses an LRU cache (lines 14-20) and internal utilities like `fixFilePaths` and `fixImport` to normalize paths and handle cross-style imports.

## Frequently Asked Questions

### What is the purpose of the [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) file?

The [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) file acts as the declarative manifest for a specific style (e.g., `new-york-v4`). It exports an array of registry items that describe component names, types, dependencies, and file paths. The build script scans these files to generate the runtime [`__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/__index__.tsx), making [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) the single source of truth for what components are available in a given style.

### How does the build-registry script work?

Located at `apps/v4/scripts/build-registry.mts`, the script recursively scans all [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) files across registry folders. It validates entries against `registryItemSchema` from [`packages/shadcn/schema/index.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/schema/index.ts), resolves file paths, and generates [`apps/v4/registry/__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/__index__.tsx). This generated file exports an `Index` object that maps style names to lazy-loaded component factories, enabling the runtime helpers to resolve components without dynamic imports at the call site.

### Can I use custom components without regenerating the index?

No. The runtime helpers in [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts) consume the generated [`__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/__index__.tsx) to resolve component paths and metadata. If you add a component to [`_registry.ts`](https://github.com/shadcn-ui/ui/blob/main/_registry.ts) but do not regenerate the index, the `Index` object will not contain the new entry, and `getRegistryComponent` will return `undefined`. You must run `pnpm run build-registry` or rely on the watch mode to update [`__index__.tsx`](https://github.com/shadcn-ui/ui/blob/main/__index__.tsx).

### What is the difference between `getRegistryComponent` and `getRegistryItem`?

`getRegistryComponent` (lines 75-84 in [`apps/v4/lib/registry.ts`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/lib/registry.ts)) returns a React component that is ready to render. It first checks for a demo component, then falls back to the lazy-loaded component from the generated `Index`. In contrast, `getRegistryItem` (lines 85-115) returns the raw registry metadata, including file contents, paths, and dependencies. Use `getRegistryComponent` for rendering UI, and `getRegistryItem` when building code-generation tools or documentation that needs to inspect source code.