TL;DR List your components in registry.json (name and type are required). Run npx shadcn@latest build to get one JSON file per item in public/r and host it anywhere static, or put registry.json at the root of a public GitHub repo and install as owner/repo/item. For v0, link to https://v0.dev/chat/api/open?url= plus the item URL and skip cssVars, css, envVars and namespaces.
A shadcn registry is a registry.json file that lists your components, plus one JSON file per component, served over HTTP. Once it's online, anyone (future you included) installs a component with npx shadcn@latest add <url>, and the CLI writes the source files into their project and installs the npm packages they need. Static hosting like GitHub Pages is enough. v0 can open the same URL through its Open in v0 endpoint, and a public GitHub repo with registry.json at its root now works as a registry with no hosting step at all.
I make qodesign, a set of React + TypeScript components with Tailwind (most also use Framer Motion). We publish six free pieces as a registry from our MIT repo qodesign-free, including the Day / Night Toggle. Everything below comes from that setup and from the shadcn registry docs as I read them on October 21, 2026. If you want the other side, installing from someone else's registry, this post covers it.
If you build with Cursor, v0 or Lovable, you probably have a handful of components you've tuned by hand and now paste from project to project. Copy-paste drops three things on the way: the npm packages the file imports, where the file is supposed to live, and every fix you make later. A registry item carries all three.
The person installing still owns the code. The CLI writes plain .tsx files into their repo, so there's no package version to keep in sync and nothing to eject from. That's the same deal shadcn/ui itself offers, which is why it fits AI editors well: the agent can read and edit the installed file like any other file in the project.
Day / Night Toggle running live. A theme switch with a sun that sets into a starry night.
The root file names the registry and lists its items. Each item has to match the registry-item schema, where only name and type are required. Here is our real entry for the toggle, with the homepage shortened:
json
{ "$schema": "https://ui.shadcn.com/schema/registry.json", "name": "qodesign-free", "homepage": "https://qodesign.dev", "items": [ { "name": "day-night-toggle", "type": "registry:component", "title": "Day / Night Toggle", "description": "Theme switch where the sun rolls into a moon, clouds fall away and stars drop in. Free, MIT, by qodesign.dev.", "dependencies": [ "framer-motion" ], "files": [ { "path": "components/day-night-toggle/react/src/components/DayNightToggle.tsx", "type": "registry:component", "target": "components/day-night-toggle/DayNightToggle.tsx" } ] } ]}
What each field does:
path is where the file lives in your repo. target is where it lands in theirs.
Without target, the CLI reads the person's components.json to decide where the file goes. The docs say target is only required for registry:page and registry:file. We set it anyway so each piece lands in its own folder.
A target can start with @ui/, @components/, @lib/ or @hooks/. Those resolve to the aliases in the person's components.json, so the file respects a src/ layout or a monorepo package without you hardcoding paths.
dependencies are npm packages the CLI installs. List every import that isn't React. Ours is just framer-motion.
registryDependencies are other registry items, like button from shadcn/ui or a full URL to another item of yours.
For type, the docs list twelve values. The ones you'll use most: registry:ui for single-file primitives, registry:component for simple components, registry:block for multi-file pieces, plus registry:hook, registry:lib, and registry:page or registry:file for routes and config (both need a target). Our two dashboards are blocks and the four small pieces are components.
npx shadcn@latest build reads registry.json and writes one JSON file per item to public/r by default (--output changes the folder). Each generated file repeats the item's fields and adds a content string holding the full source of every file.
You can see ours at /r/day-night-toggle.json. The toggle's source is inlined as 4,919 characters, starting with 'use client';. That generated file is what the CLI downloads when someone runs add.
One thing to check before you build: the CLI copies your file as it is. If the component uses state, effects or Framer Motion, the 'use client' line has to be in your source, or it will break the first time someone imports it into a Server Component page.
Static JSON files. Put the built r/ folder on any static host: GitHub Pages, Vercel, Netlify or the public folder of a Next.js app. Ours lives on GitHub Pages, and the README documents this install:
A GitHub registry. Since shadcn added GitHub registries, a public repo with registry.json at the root installs as owner/repo/item, with no generated JSON to publish:
The docs list four requirements: a github.com repository, registry.json at the root, valid schemas, and source files that exist at the paths you declare. Our repo meets all four on paper, but I haven't run this short address against it yet, so the Pages URL above is the one we document.
A namespace. Add your registry to registries in components.json with a {name} placeholder, then install by short name:
Namespace names start with @ and use only letters, numbers, hyphens and underscores. The docs point to namespaces when you need a stable alias, custom hosting, request headers or a private registry. For a solo builder sharing free pieces, a GitHub registry or static files are enough to start.
If an item is public, v0 opens it from https://v0.dev/chat/api/open?url= followed by the item's URL. The docs' example wraps a shadcn Button. This version is a plain link, so it works without any other component (it compiles under strict TypeScript with React 19 types):
tsx
type OpenInV0LinkProps = { itemUrl: string };export function OpenInV0Link({ itemUrl }: OpenInV0LinkProps) { return ( <a href={`https://v0.dev/chat/api/open?url=${itemUrl}`} target="_blank" rel="noreferrer" aria-label="Open in v0" className="inline-flex h-8 items-center rounded-md bg-black px-3 text-xs font-medium text-white dark:bg-white dark:text-black" > Open in v0 </a> );}
The docs list what Open in v0 doesn't support: cssVars, css, envVars, namespaced registries and advanced authentication. Items you want people to open in v0 should stick to files and dependencies, and the button should use the full item URL, not a @namespace/item name.
No. The docs say any framework that serves JSON over HTTP works, and so does a public GitHub repo with registry.json at the root. The template is a starting point if you want one.
Yes. The GitHub registry docs list config files, docs, templates, AGENTS.md and .cursor/rules/* as examples. Use registry:file with a target that starts with ~/ to place a file relative to the project root, which is a tidy way to share your Cursor rules across projects.
They own the files once they're installed, so an update means running add again and reviewing the diff. Keep your item names stable so the same command keeps working.