Why AtroUI moved from npm install to the shadcn registry (and what stayed on npm)
A detailed look at migrating AtroUI from a classic npm UI package to a shadcn-compatible registry: ownership, updates, Host APIs, and how to migrate an existing app.
For a long time the natural way to ship a React design system was simple: publish an npm package, tell people to npm install atroui, import from atroui/…, and hope transpilePackages plus peer deps lined up.
That model still works for some libraries. It stopped being the right *consumer* story for AtroUI. The catalog is now a shadcn-compatible registry: you run npx shadcn add @atroui/home-hero, source lands in your repo, and you edit CONTENT at the top of the file.
This post explains what we migrated, why, what still lives on npm, and how to move an app that already depended on the package.
What the old npm-first path looked like
Early AtroUI leaned on the classic library contract:
# Install
npm i atroui next-themes
# next.config.ts
transpilePackages: ["atroui"]
# Import UI from the package
import { HomeHero } from "atroui/components/…"
import "atroui/globals.css"That is fine when every consumer wants the *same* locked component binary. It fights you when every consumer wants to rewrite copy, swap CTAs, fork layout, and ship tomorrow.
Where npm-as-UI started to hurt
Black-box ownership. A hero that lives only in node_modules is awkward to restyle. Teams either monkey-patched props forever or forked the package. The shadcn ecosystem already solved that by copying source into the app.
Update friction in the wrong place. Semver bumps for a wording tweak or a class rename force a dependency upgrade dance across every consumer. When the file lives in *your* repo, you merge what you want and ignore the rest.
Install surface area. Fresh App Router apps hit Turbopack/webpack errors until transpilePackages was set. Peers were easy to forget. Docs had to teach library packaging before teaching design. See transpilePackages and Turbopack.
Wrong altitude for marketing UI. Atoms in a package make sense. Full page bands with studio demo copy locked behind an import path do not. AtroUI’s job is production sections - heroes, who bands, footers, CTAs - with editable constants on day one.
Discovery. Designers and indie makers already know npx shadcn add. Teaching a second install religion for the same class of UI slows adoption.
What we migrated to
AtroUI now leads with the registry on atroui.com:
npx shadcn@latest init
npx shadcn@latest registry add @atroui=https://www.atroui.com/r/{name}.json
npx shadcn@latest add @atroui/home-hero
npx shadcn@latest add @atroui/site-header @atroui/site-footerEach item is a JSON registry entry that points at source under apps/docs/registry/. The CLI copies files into your aliases (components/blocks/…, lib/brand.ts, and so on). Dependencies resolve as @atroui/brand, @atroui/utils, etc. - registry names, not opaque package internals.
You own the files. Diff them. Delete what you do not need. Rebrand by editing DEFAULT_BRAND or setting NEXT_PUBLIC_SITE_*. That is the same ownership model as shadcn/ui, aimed at a higher altitude: AtroUI vs shadcn/ui.
Why the shadcn ecosystem specifically
It is already the default distribution channel for copy-into-repo UI in Next.js land. Fighting that means inventing a second CLI, a second docs language, and a second mental model.
Registries compose. Teams can keep blank-slate primitives from one registry and AtroUI sections from another, as long as tokens do not fight. We document that path on Compare.
Directory and tooling. A public https://www.atroui.com/r/{name}.json URL works with the stock CLI, CI checks, and the emerging shadcn directory surface. See our directory notes.
Docs and product stay aligned. The same registry that powers consumer installs also builds the live catalog on atroui.com. What you add is what we demo.
What we deliberately kept on npm
The migration is not “delete the package.” It is “stop pretending every UI file should be consumed as a versioned black box.”
The published atroui package (currently 0.2.3, with a pending 0.3.0 minor for Host API handlers) still matters for:
Host API handlers under atroui/api/* - contact, waitlist, newsletter, generate, thumbnail, scope. These share validation, honeypots, body caps, rate limits, and image compose logic (Satori, resvg, sharp). Vendoring that into every app via the CLI would ship native .node addons and font paths into consumer trees in painful ways.
The docs monorepo itself - @atroui/docs depends on atroui: workspace:* so the marketing site and API routes can import the same handlers.
Optional atroui/globals.css for hosts that already install the package.
So the product has two install modes (also documented on Installation):
# Registry UI only - no npm package required
npx shadcn@latest add @atroui/home-hero
# Host APIs - package + thin route stubs
npm i atroui
# next.config.ts → transpilePackages: ["atroui"]
npx shadcn@latest add @atroui/api-contact @atroui/api-generateAtroUI never ships API keys and does not run paid AI on atroui.com. BYOK stays in *your* env. That rule is easier to enforce when secrets never live in copied UI files.
How the monorepo changed shape
The repo split is deliberate: registry for UI you own, package for Host API machinery.
apps/docs/registry/ # source of truth for copy-paste items
pnpm registry:build # emits apps/docs/public/r/*.json for the CLI
packages/ui # publishable atroui (handlers, compose, tests)
apps/docs/app/api/* # thin wrappers (= @atroui/api-* stubs)Consumer-facing README and install docs lead with the CLI. npm is documented where Host APIs need it, not as the default hero path.
Migrating an existing npm-based app
If you already import … from "atroui/…" for UI, plan a deliberate cutover rather than a big-bang delete.
# 1. Register the catalog
npx shadcn@latest registry add @atroui=https://www.atroui.com/r/{name}.json
# 2. Add the blocks you actually use (example)
npx shadcn@latest add @atroui/home-hero @atroui/site-header @atroui/brand @atroui/utils
# 3. Point imports at local files (@/components/…, @/lib/brand)
# 4. Copy CONTENT / DEFAULT_BRAND values you already customized
# 5. Keep or add npm atroui only if you use Host APIs
npm i atroui # optional - Host API consumers onlyReplace from "atroui/components/…" with imports from the files the CLI wrote.
Replace from "atroui/lib/brand" with @/lib/brand (or your alias) after adding @atroui/brand.
Move token ownership into the host CSS sheet you already maintain. Keep atroui/globals.css only if you still depend on the package for other reasons.
If you use contact / OG / thumbnail / scope, install the matching @atroui/api-* routes and leave transpilePackages: ["atroui"] in place.
Delete unused package imports last. Run the app, then drop atroui from package.json only if nothing under atroui/api/* remains.
Versioning after the split
Registry items are not semver’d the same way as npm. The JSON on atroui.com is what the CLI fetches; your copied files version with *your* git history.
The npm package still uses Changesets. Pending notes bump atroui to 0.3.0 for the Host API surface (atroui/api/contact|waitlist|newsletter|generate|thumbnail|scope). Until that release ships, published line remains 0.2.x (see Changelog and SECURITY).
Docs app @atroui/docs@0.1.0 is private and ignored by Changesets - that version is not the library version.
What we optimized for
Speed to a coherent dark UI you can edit.
Same CLI muscle memory as the rest of the Next.js ecosystem.
A clean boundary for secrets and native image tooling via Host APIs.
One canonical brand home: www.atroui.com.
Next steps
New project: Install AtroUI in a Next.js App Router project.
Catalog: Registry.
Brand: Rebrand with getBrand().
Positioning: What is AtroUI?.
Lessons from shipping: Registry + package internals.