Why CLI-Installed Components Are Replacing npm Packages
For a decade, the way to share React components was npm. Publish a package, consumers install it, import the components. This model works well for utility libraries like date-fns or zod — stable APIs, no visual opinions, infrequent breaking changes.
For UI components, it's a terrible fit.
The Three Problems with Component Packages
1. Version Lock
When you install a component library as a dependency, you're locked to its API surface. The maintainer renames a prop? Your build breaks on the next npm update. They remove a variant you depend on? Time to fork.
You can't customize internals without ejecting the entire package. And ejecting means you're now maintaining a fork — the worst of both worlds.
2. Style Conflicts
Component packages bundle their own styles. Their button CSS fights your button CSS. You end up with !important overrides, specificity wars, or giving up and adopting their entire design system.
This is especially painful with Tailwind, where the whole point is utility-first styling. A packaged component that ships its own Tailwind config creates merge conflicts with yours.
3. Bundle Bloat
You import one dialog component. Webpack pulls in the entire library because CSS side effects prevent proper tree-shaking. Your bundle grows by 150KB for a component you could have written in 80 lines.
The Copy-to-Project Model
The alternative: a CLI that copies component source code directly into your project.
npx some-registry add dialog
This creates src/components/ui/dialog.tsx in your project. It's your file. Modify it, delete half of it, add props, change styles. No dependency in package.json, no version to track, no lock-in.
How It Works
- A registry defines each component's metadata: files, npm dependencies, and peer components
- An API serves the component source code as JSON
- The CLI fetches the definition, resolves your project paths, installs missing npm deps, and writes the files
- A diff command shows what changed upstream since you installed — you decide what to merge
The key insight: npm dependencies (like @radix-ui/react-dialog) are still installed normally. The component *source code* is what gets copied. You get the stable library ecosystem for behavior and full ownership for presentation.
The Trade-Off
You lose automatic updates. When the upstream component improves, you don't get it for free. You run diff to see what changed and decide whether to pull it in.
For utility libraries, automatic updates are valuable — bug fixes and performance improvements flow downstream automatically.
For UI components, automatic updates are dangerous. A "minor" visual change can break your design system. A prop rename cascades through dozens of files. Production teams don't want surprise changes to their checkout page.
Controlled, reviewed updates match how professional teams actually ship. The copy model makes this the default behavior instead of something you opt into.
Why Now?
Three things converged:
- shadcn/ui proved the market — millions of installs showed developers prefer ownership over packages for UI code
- TypeScript matured — copied code with full type definitions is self-documenting and IDE-friendly
- Tailwind won — utility-first CSS means component styles are in the markup, not in a separate stylesheet that conflicts with yours
The package model isn't dead. It's the right answer for lodash, for ORMs, for auth SDKs. But for the visual layer of your app — the buttons, dialogs, tables, and layouts that define your product's identity — copying the source and owning it is strictly better.
What to Look For in a Component Registry
If you're evaluating CLI-installable component libraries, check for:
- Selective installation — install individual components, not the whole kit
- Dependency resolution — the CLI should install required npm packages automatically
- Diff support — see what changed upstream before merging
- TypeScript types — every prop typed, every export documented
- No runtime dependency — installed components shouldn't import from the registry package
The best component registry is the one that disappears after installation. Your code, your project, your rules.