How to install components from a custom shadcn registry

Install shadcn components from any registry URL: what the item JSON contains, how registryDependencies resolve, components.json setup and common fixes.

By MiniDev9 min read

The shadcn CLI installs from any URL that returns a registry item JSON: npx shadcn@latest add https://ui.minidev.pro/r/button.json. It downloads the item, installs its npm packages, follows its registryDependencies to fetch other items, and writes the source files into your project with imports rewritten to your aliases. All you need is a components.json at the project root, which npx shadcn@latest init creates.

How shadcn add installs from a URL

The add command accepts three kinds of arguments. A bare name like button resolves against the default shadcn registry. A namespaced name like @minidev/button resolves through the registries map in your components.json. A full URL is fetched as is. You can mix them and pass several at once.

bash
# one item
npx shadcn@latest add https://ui.minidev.pro/r/button.json

# several items in one run
npx shadcn@latest add \
  https://ui.minidev.pro/r/button.json \
  https://ui.minidev.pro/r/segmented-control.json

For each argument the CLI does roughly the same work:

  1. Fetches the JSON and validates it against the registry item schema (the $schema field points at https://ui.shadcn.com/schema/registry-item.json).
  2. Walks registryDependencies recursively and collects every item in the tree, so shared dependencies are installed once.
  3. Installs the combined dependencies list with the package manager it detects from your lockfile.
  4. Writes each entry in files to disk and rewrites @/ imports to match the aliases in your components.json.
  5. Asks before overwriting a file that already exists, unless you pass --overwrite.

Nothing is linked or bundled. The component becomes your code, which is the whole point of the shadcn model: you can read it, edit it and delete what you do not need.

What is inside a registry item JSON

Open any MiniDev item in the browser, for example button.json. With the file content shortened, it looks like this:

https://ui.minidev.pro/r/button.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "type": "registry:ui",
  "title": "Button",
  "description": "The Hairline keycap.",
  "dependencies": ["@base-ui/react", "class-variance-authority"],
  "registryDependencies": ["utils"],
  "files": [
    {
      "path": "registry/ui/button.tsx",
      "type": "registry:ui",
      "target": "components/ui/button.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\n..."
    }
  ]
}
FieldWhat the CLI does with it
nameThe item's id. Other items refer to it by this name or by its URL.
typeWhat kind of item it is: registry:ui for primitives, registry:component for composed pieces, plus types like registry:block, registry:hook and registry:lib.
dependenciesnpm packages to install. Versions are allowed (motion@12), bare names get the latest.
registryDependenciesOther registry items to install first, by name or URL.
files[].contentThe full source, as a JSON string. This is what gets written to disk.
files[].targetWhere the file goes, relative to the project root.
cssVars / cssOptional theme variables and CSS rules merged into your stylesheet. MiniDev items leave these out and ship tokens as one stylesheet instead.

MiniDev sets target to components/ui/<name>.tsx for every item, blocks included, so everything you add lands in one folder. The source in the repo imports siblings as @/registry/ui/button; the build script rewrites those to @/components/ui/button before writing the JSON, and the CLI then maps @/components/ui and @/lib/utils onto whatever aliases your project uses.

How registryDependencies pull in other items

A block is just an item with more dependencies. Here is the metadata for pricing-plans, which imports three other MiniDev components:

https://ui.minidev.pro/r/pricing-plans.jsonjson
{
  "name": "pricing-plans",
  "type": "registry:component",
  "dependencies": ["lucide-react"],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "https://ui.minidev.pro/r/number-roll.json",
    "https://ui.minidev.pro/r/segmented-control.json",
    "utils"
  ]
}

The CLI fetches each URL, reads that item's own dependencies, and keeps going until the tree is complete. button needs @base-ui/react and class-variance-authority; number-roll and segmented-control need motion. The bare utils entry resolves against the default shadcn registry and writes lib/utils.ts with the cn() helper, which pulls in clsx and tailwind-merge. One command therefore produces:

  • components/ui/pricing-plans.tsx, button.tsx, number-roll.tsx and segmented-control.tsx
  • lib/utils.ts (skipped if you already have it)
  • npm installs for lucide-react, @base-ui/react, class-variance-authority, motion, clsx and tailwind-merge

A bare name in registryDependencies always means the default shadcn registry. If a third party registry lists "button" instead of its own URL, you get shadcn's button, not theirs. That is why every MiniDev dependency except utils is a full URL.

Set up components.json first

Run npx shadcn@latest init once per project. It detects your framework and Tailwind version and writes components.json. For Tailwind CSS v4 the tailwind.config field is empty, because configuration lives in CSS. The aliases must match the paths in your tsconfig.json, or the rewritten imports will not resolve.

components.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "registries": {
    "@minidev": "https://ui.minidev.pro/r/{name}.json"
  }
}

Namespaced registries

The registries map is optional. With it, the CLI replaces {name} in the template, so npx shadcn@latest add @minidev/pricing-plans fetches https://ui.minidev.pro/r/pricing-plans.json. Namespaces need shadcn CLI 3.0 or newer. Full URLs work in every version and in projects you do not control, which is why the MiniDev docs show them.

Add the design tokens

Component files reference semantic classes like bg-surface, text-fg-muted and shadow-raised. Those come from one stylesheet, not from the item JSON. Either install the npm package and import it, or download styles.css next to your global CSS and import the local copy.

app/globals.csscss
@import "tailwindcss";

/* Option A: the npm package */
@import "minidev-ui-kit/styles.css";
@source "../node_modules/minidev-ui-kit";

/* Option B: a local copy of https://ui.minidev.pro/r/styles.css */
/* @import "./minidev.css"; */

The stylesheet also defines the standard shadcn variables (--background, --primary, --ring and so on), mapped onto MiniDev tokens, so stock shadcn components pick up the theme. Delete the :root and .dark blocks that init generated, or they will override the import. One difference to know: in MiniDev accent is the brand violet, while stock shadcn uses accent as a neutral hover fill.

For how the tokens are built and how dark mode swaps them, see OKLCH colors in Tailwind CSS v4.

Installing blocks and full pages

Blocks and page sections use the same URL scheme and the same command. After the install, import from the folder the CLI wrote to:

bash
npx shadcn@latest add https://ui.minidev.pro/r/pricing-plans.json
app/pricing/page.tsxtsx
import { PricingPlans } from "@/components/ui/pricing-plans"

export default function PricingPage() {
  return (
    <PricingPlans
      currency="EUR"
      onSelect={(plan, period) => console.log(plan, period)}
    />
  )
}

Every prop on PricingPlans has a default, so it renders with sample plans before you pass your own. The React pricing page guide covers wiring it to real plan data.

Troubleshooting

The CLI cannot find components.json

Run the command from the project root, or point it at the right folder with --cwd (short form -c). In a monorepo, each app that receives components needs its own components.json.

The URL returns an error or HTML

The CLI needs raw JSON. Check the URL with curl -s <url> | head. Common causes are a missing .json extension, a trailing slash, a redirect to a login page, or a registry that serves its index at registry.json rather than per item files.

bash
curl -s https://ui.minidev.pro/r/pricing-plans.json \
  | jq '{name, dependencies, registryDependencies, files: [.files[] | .target]}'

A file already exists

Two registries can both ship components/ui/button.tsx. If you already have shadcn's button and install a MiniDev block, the CLI asks whether to overwrite it. Keeping yours only works if the block uses props your version supports: pricing-plans renders <Button variant="accent">, which stock shadcn does not define. Use --overwrite only after committing, so you can review the diff.

Updating a component you already installed

There is no lockfile for registry items. To pick up a newer version, run the same add command again with --overwrite, then review the change with git diff and restore any local edits you want to keep. Because the code is yours, updates are merges you control, not upgrades that happen to you.

Imports do not resolve

If TypeScript cannot find @/lib/utils or @/components/ui/button, the aliases in components.json and the paths in tsconfig.json disagree. Fix the aliases, then reinstall the item so the imports are rewritten again.

Components render unstyled

Classes like bg-surface compile to nothing if the tokens are not imported, because Tailwind v4 only generates utilities for colors defined in @theme. Confirm the stylesheet import comes after @import "tailwindcss". If you use the npm package, also keep the @source line so Tailwind scans the package files.

Start with MiniDev UI

Every MiniDev component and block is a public registry item under https://ui.minidev.pro/r/. Each docs page shows the exact install command. Two good first installs: the button, which most blocks depend on, and a full block to see dependency resolution at work.

ButtonuiShadcn compatible React button on Base UI with eight variants, nine sizes including icon sizes, a pressed state and render prop support for links.npx shadcn@latest add ui.minidev.pro/r/button.jsonPricing PlansblockA React pricing section with a monthly or yearly switch that rolls prices to the new value, a featured ink plan, and feature rows marked included or not.npx shadcn@latest add ui.minidev.pro/r/pricing-plans.json
bash
npx shadcn@latest add https://ui.minidev.pro/r/button.json
npx shadcn@latest add https://ui.minidev.pro/r/pricing-plans.json

Components used in this guide

Frequently asked questions

What is a shadcn registry?

A set of JSON files that follow the shadcn registry item schema, served over HTTP. Each file holds a component's source plus its npm and registry dependencies, and the shadcn CLI copies it into your project.

Can shadcn add install from a URL?

Yes. npx shadcn@latest add https://example.com/r/item.json works with any URL that returns a valid registry item. You can also register a namespace in components.json and use @namespace/item.

What does registryDependencies do?

It lists other registry items the component needs. The CLI installs them first, recursively. Bare names resolve to the default shadcn registry, and full URLs are fetched directly.

Do I need components.json to install from a custom registry?

Yes. The CLI reads it to know where to write files, which aliases to use in imports and where your global CSS lives. Run npx shadcn@latest init to create it.

How do I host my own shadcn registry?

Describe your items in a registry.json and run npx shadcn@latest build, which writes one JSON file per item to public/r. Deploy that folder to any static host and install with the URLs.

More guides