Registry setup
Consume or self-host the registry.
Join UI ships a real shadcn-compatible registry: a JSON index plus one installable item per component, generated and validated during the build.
Consuming the registry
Add the namespace to components.json:
{
"registries": {
"@joinui": "https://ui.join-way.com/r/{name}.json"
}
}{name} is substituted by the CLI, so @joinui/<component> resolves to
https://ui.join-way.com/r/<component>.json.
pnpm dlx shadcn@latest add @joinui/<component>Or skip the namespace entirely:
pnpm dlx shadcn@latest add https://ui.join-way.com/r/<component>.jsonEndpoints
| URL | Contents |
|---|---|
/r/registry.json | Index of every item, without file bodies |
/r/<name>.json | A single installable item, with full source inlined |
Both are static files served with Access-Control-Allow-Origin: *, so the CLI
can read them from any project.
Anatomy of an item
Every item has the same shape. This is what one would look like for a
hypothetical glow-card:
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "glow-card",
"type": "registry:ui",
"title": "Glow Card",
"description": "A bounded panel that raises its rule on hover.",
"author": "Join Way",
"dependencies": [],
"registryDependencies": ["utils"],
"files": [
{
"path": "registry/components/glow-card.tsx",
"type": "registry:ui",
"target": "components/joinui/glow-card.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\n…"
}
],
"categories": ["cards", "layout"],
"docs": "https://ui.join-way.com/components/glow-card",
"meta": { "status": "stable", "since": "2026-08-02", "featured": false }
}path is where the file lives in this repository; target is where it lands in
yours. meta is free-form — the CLI ignores it, and this site uses it to keep
the published item self-describing.
Generation and validation
Items are never written by hand. pnpm registry:build projects them from the
component metadata in lib/registry/components.ts:
pnpm registry:build✓ Registry built — 1 items, 13.2 kB of source
registry.json
public/r/registry.json
public/r/*.json (1 files)The counts track lib/registry/components.ts exactly — one entry there is one
item here, and the byte count is the source inlined into the item files. The
catalog is still short while the component set is rebuilt against the current
design system, so expect those numbers to be small and to climb.
The build refuses to emit anything until validation passes:
pnpm registry:validateValidation covers the things a schema alone cannot:
- metadata parses against a Zod schema (kebab-case slugs, PascalCase names, ISO dates, description length);
- every file a component declares actually exists on disk;
- every component has a preview registered in
components/previews/registry.tsx; relatedpoints only at slugs that exist, and never at itself;- the generated item still parses as a valid registry item, with non-empty file contents.
Self-hosting your own registry
The same pipeline works for a private design system.
Describe a component
One entry in lib/registry/components.ts. defineComponent derives the
install command so the namespace lives in exactly one place.
defineComponent({
name: "GlowCard",
slug: "glow-card",
title: "Glow Card",
description: "A card with an animated glow.",
overview: "…",
category: "Cards",
tags: ["card", "glow"],
status: "new",
featured: false,
dependencies: [],
registryDependencies: ["utils"],
files: [uiFile("glow-card")],
accessibility: ["…"],
keyboard: [],
props: [{ name: "GlowCard", props: [] }],
usage: "…",
customization: [],
related: [],
since: "2026-08-02",
})Build and host
pnpm registry:buildDeploy the site. public/r/*.json is served as static JSON — any host will
do, including a plain object store behind a CDN.
Point consumers at it
{
"registries": {
"@acme": "https://design.acme.com/r/{name}.json"
}
}Private registries
The CLI supports headers on a registry entry, so a token from the environment is enough to gate access:
{
"registries": {
"@acme": {
"url": "https://design.acme.com/r/{name}.json",
"headers": { "Authorization": "Bearer ${ACME_TOKEN}" }
}
}
}Serve /r/* behind the check of your choice and drop the wildcard CORS header
for those routes.
Registry file types
| Type | Used for |
|---|---|
registry:ui | A component — what every Join UI item is |
registry:lib | A utility module |
registry:hook | A React hook |
registry:component | A composed, app-level component |
registry:file | Anything else, placed verbatim at target |
Join UI keeps every component to a single registry:ui file on purpose: one
install, one file to read, one file to delete.