# How shadcn/ui's Copy-Paste Component Architecture Differs from Traditional npm Libraries

> Discover how shadcn/ui's copy-paste architecture differs from npm libraries. Gain control over code and eliminate dependencies bypassing node_modules.

- Repository: [shadcn-ui/ui](https://github.com/shadcn-ui/ui)
- Tags: deep-dive
- Published: 2026-02-26

---

**shadcn/ui uses a copy-paste workflow where the CLI copies source files directly into your project, eliminating runtime dependencies and giving you full control over the code, unlike traditional npm packages that remain as external dependencies in node_modules.**

The shadcn/ui repository revolutionizes how developers consume UI components by replacing the traditional `npm install` workflow with a deterministic copy-paste architecture. Instead of importing compiled bundles from `node_modules`, the shadcn CLI pulls raw TypeScript and Tailwind source files directly into your codebase. This approach fundamentally changes how components are distributed, customized, and maintained compared to conventional npm libraries.

## Distribution Model: Registry Files vs. Compiled Bundles

Traditional component libraries distribute code as compiled JavaScript bundles through npm. When you run `npm install @some/library`, you receive minified files in `node_modules` that your bundler imports at runtime.

In contrast, shadcn/ui distributes components as raw source files through a public registry. The registry is defined in JSON files such as [`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json), which map component names to source file URLs and metadata. When you request a component, the CLI fetches these raw TSX files, utility helpers, and Tailwind CSS configurations directly from the registry rather than installing a pre-built package.

## The Copy-Paste Installation Flow

The installation process is orchestrated by the shadcn CLI, which treats component installation as a file-writing operation rather than a package manager transaction.

### CLI Command Execution

When you run `npx shadcn@latest add button`, the CLI executes the logic defined in [`packages/shadcn/src/commands/add.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/commands/add.ts). This command initializes the add workflow by parsing arguments and delegating to the component resolution logic.

The actual file operations are handled by [`packages/shadcn/src/utils/add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/add-components.ts), which coordinates the entire copy-paste process. This utility resolves the component from the registry, downloads the necessary files, and writes them into your project's directory structure.

### Registry Resolution

Before files can be copied, the CLI must resolve the component's location in the registry. This is handled by [`packages/shadcn/src/registry/api.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/api.ts), which fetches component metadata from the registry JSON files.

The URL construction and request handling logic resides in [`packages/shadcn/src/registry/builder.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/builder.ts). This builder generates the proper endpoints to retrieve raw source files from the registry, ensuring the CLI pulls the correct TypeScript and CSS files for each component.

### Configuration Updates

A critical part of the copy-paste architecture is the automatic configuration merging. Unlike traditional libraries that require manual Tailwind setup, shadcn/ui updates your project's configuration to match the component's requirements.

The [`packages/shadcn/src/utils/updaters/update-tailwind-config.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/updaters/update-tailwind-config.ts) file contains the logic for merging Tailwind configurations. When you add a component, the CLI analyzes the required Tailwind classes and updates your [`tailwind.config.ts`](https://github.com/shadcn-ui/ui/blob/main/tailwind.config.ts) accordingly.

Similarly, the CLI handles CSS variables, fonts, and dependencies through dedicated updater utilities. This ensures that copied components work immediately without manual configuration, while still living as editable source code in your repository.

## Runtime and Maintenance Implications

The copy-paste architecture creates fundamental differences in how applications depend on and maintain UI components.

### Zero Runtime Dependencies

Once a component is copied into your project, it becomes part of your codebase. There is **no runtime dependency** on the shadcn/ui package itself. The component imports from your local utility files (such as `@/lib/utils`) and uses your project's Tailwind configuration.

This eliminates the risk of version conflicts, peer dependency issues, or supply chain attacks from upstream packages. Your bundle contains only the code you explicitly copied, with no hidden transitive dependencies from a component library.

### Full Source Control and Customization

Because the source files live in your repository, you can edit the JSX markup, modify Tailwind utility classes, or replace component internals without forking a library. If a button component needs a different hover state, you simply edit [`src/components/ui/button.tsx`](https://github.com/shadcn-ui/ui/blob/main/src/components/ui/button.tsx) directly.

Traditional npm libraries require style overrides through props, CSS-in-JS overrides, or complex theming systems. With shadcn/ui's copy-paste model, customization happens at the source code level, providing unlimited flexibility to adapt components to your specific design requirements.

### Version Management Differences

Updating components requires re-running the CLI to fetch the latest source files from the registry. The CLI records component versions in the registry metadata ([`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json)), allowing you to selectively update individual components rather than upgrading an entire library.

This granular approach contrasts with traditional npm updates, where `npm update` pulls new versions for the entire package, potentially introducing breaking changes across all components simultaneously. However, the trade-off is that you must manually run `npx shadcn add <component>` to receive updates, and the copied files require version control in your repository.

## Summary

- **shadcn/ui** distributes components as raw TypeScript and Tailwind source files through a JSON registry ([`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json)), while traditional libraries distribute compiled bundles via npm.
- The **CLI copy-paste workflow** ([`packages/shadcn/src/commands/add.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/commands/add.ts) and [`add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/add-components.ts)) writes component files directly into your project, eliminating runtime dependencies on the shadcn package.
- **Automatic configuration merging** via updater utilities ([`update-tailwind-config.ts`](https://github.com/shadcn-ui/ui/blob/main/update-tailwind-config.ts)) ensures copied components work immediately with your existing Tailwind and CSS setup.
- **Full source ownership** allows unlimited customization by editing the copied files, while traditional npm libraries require overrides or forks for deep modifications.
- **Zero runtime overhead** means no peer dependency conflicts or bundle bloat from unused library features.

## Frequently Asked Questions

### How does the shadcn CLI know which files to copy?

The CLI resolves component locations through the registry API ([`packages/shadcn/src/registry/api.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/api.ts)), which reads the public registry defined in [`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json). This JSON file maps component names to their source file URLs and metadata. The builder utility ([`packages/shadcn/src/registry/builder.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/registry/builder.ts)) constructs the proper endpoints to fetch these raw TypeScript and CSS files, which are then written to your project by the [`add-components.ts`](https://github.com/shadcn-ui/ui/blob/main/add-components.ts) utility.

### Can I update a shadcn component after I've modified it?

Yes, but with caveats. Since the component source lives in your repository, you own the code completely. To update to the latest version from the registry, you would run `npx shadcn add <component>` again, which would overwrite your local files. If you've made customizations, you'll need to merge the changes manually or re-apply your modifications after the update. This differs from traditional npm packages where updates happen atomically via `npm update`, but you cannot modify the source directly.

### Does shadcn/ui add any runtime dependencies to my project?

No. Once the CLI copies the component files into your project (typically under `src/components/ui/`), those components import from your local utility files (such as `@/lib/utils`) and use your project's Tailwind configuration. The shadcn/ui CLI itself is a development tool, not a runtime dependency. This eliminates peer dependency conflicts, reduces bundle size, and removes the risk of supply chain attacks from upstream UI library updates.

### How does shadcn handle Tailwind configuration for copied components?

When you add a component, the CLI automatically analyzes the required Tailwind classes and updates your configuration files. The [`packages/shadcn/src/utils/updaters/update-tailwind-config.ts`](https://github.com/shadcn-ui/ui/blob/main/packages/shadcn/src/utils/updaters/update-tailwind-config.ts) utility merges the necessary Tailwind settings into your [`tailwind.config.ts`](https://github.com/shadcn-ui/ui/blob/main/tailwind.config.ts). Similarly, other updaters handle CSS variables, fonts, and dependencies. This ensures that copied components work immediately without manual configuration, while still allowing you to customize the Tailwind classes directly in the component source files later.