feat: registry add command (#9351)

* feat: implement registry add

* chore: changeset

* fix: registries docs

* feat: update add command

* fix
This commit is contained in:
shadcn
2026-01-16 17:55:48 +04:00
committed by GitHub
parent 77d7b39ef7
commit 682c98989d
22 changed files with 1520 additions and 311 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": minor
---
add registry add command

View File

@@ -1,63 +0,0 @@
name: Add registry to directory
description: Add your registry to the directory
title: "[Registry Directory]: "
labels: ["registry", "directory"]
assignees: []
body:
- type: input
id: name
attributes:
label: Name
description: The name of your registry. This is also the namespace.
placeholder: e.g., "@acme"
validations:
required: true
- type: input
id: url
attributes:
label: URL
description: The URL to your registry index. Use {name} placeholder.
placeholder: https://ui.acme.com/r/{name}.json
validations:
required: true
- type: input
id: homepage
attributes:
label: Homepage
description: The URL to your registry homepage. This is where users can browse your registry.
placeholder: https://ui.acme.com
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Briefly describe what is your registry and what type of components or code it distributes.
placeholder:
validations:
required: true
- type: textarea
id: logo
attributes:
label: Logo
description: Add your SVG logo here.
placeholder:
validations:
required: true
- type: checkboxes
id: requirements
attributes:
label: Checklist
description: Verify that your registry meets the following requirements.
options:
- label: The registry must be open source and publicly accessible.
- label: The registry must be a valid JSON file that conforms to the [registry schema](https://ui.shadcn.com/docs/registry/registry-json) specification.
- label: The `files` array, if present on your registry items, must NOT include a `content` property.
- label: I've attached a square SVG logo to this issue
validations:
required: true

View File

@@ -1,13 +1,11 @@
"use client"
import * as React from "react"
import Link from "next/link"
import { IconCheck } from "@tabler/icons-react"
import { IconCheck, IconCopy, IconPlus } from "@tabler/icons-react"
import { cn } from "@/lib/utils"
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { useConfig } from "@/hooks/use-config"
import { useIsMobile } from "@/hooks/use-mobile"
import { CopyButton } from "@/components/copy-button"
import { copyToClipboardWithMeta } from "@/components/copy-button"
import { Button } from "@/registry/new-york-v4/ui/button"
import {
Dialog,
@@ -29,77 +27,114 @@ import {
DrawerTitle,
DrawerTrigger,
} from "@/registry/new-york-v4/ui/drawer"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/registry/new-york-v4/ui/tabs"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/new-york-v4/ui/tooltip"
export function DirectoryAddButton({
registry,
}: {
registry: {
name: string
url: string
}
registry: { name: string }
}) {
const { copyToClipboard, isCopied } = useCopyToClipboard()
const isMobile = useIsMobile()
const [config, setConfig] = useConfig()
const [hasCopied, setHasCopied] = React.useState(false)
const [open, setOpen] = React.useState(false)
const isMobile = useIsMobile()
const jsonValue = `{
"registries": {
"${registry.name}": "${registry.url}"
}
}`
const packageManager = config.packageManager || "pnpm"
const commands = React.useMemo(() => {
return {
pnpm: `pnpm dlx shadcn@latest registry add ${registry.name}`,
npm: `npx shadcn@latest registry add ${registry.name}`,
yarn: `yarn dlx shadcn@latest registry add ${registry.name}`,
bun: `bunx --bun shadcn@latest registry add ${registry.name}`,
}
}, [registry.name])
const command = commands[packageManager]
React.useEffect(() => {
if (hasCopied) {
const timer = setTimeout(() => setHasCopied(false), 2000)
return () => clearTimeout(timer)
}
}, [hasCopied])
const handleCopy = React.useCallback(() => {
copyToClipboardWithMeta(command, {
name: "copy_registry_add_command",
properties: {
command,
registry: registry.name,
},
})
setHasCopied(true)
}, [command, registry.name])
const Trigger = (
<Button
size="sm"
variant="outline"
className="relative z-10"
onClick={() => setOpen(true)}
>
{isCopied ? (
<IconCheck />
) : (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Model Context Protocol</title>
<path
d="M13.85 0a4.16 4.16 0 0 0-2.95 1.217L1.456 10.66a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l9.442-9.442a2.49 2.49 0 0 1 3.541 0 2.49 2.49 0 0 1 0 3.541L8.59 12.97l-.1.1a.835.835 0 0 0 0 1.18.835.835 0 0 0 1.18 0l.1-.098 7.03-7.034a2.49 2.49 0 0 1 3.542 0l.049.05a2.49 2.49 0 0 1 0 3.54l-8.54 8.54a1.96 1.96 0 0 0 0 2.755l1.753 1.753a.835.835 0 0 0 1.18 0 .835.835 0 0 0 0-1.18l-1.753-1.753a.266.266 0 0 1 0-.394l8.54-8.54a4.185 4.185 0 0 0 0-5.9l-.05-.05a4.16 4.16 0 0 0-2.95-1.218c-.2 0-.401.02-.6.048a4.17 4.17 0 0 0-1.17-3.552A4.16 4.16 0 0 0 13.85 0m0 3.333a.84.84 0 0 0-.59.245L6.275 10.56a4.186 4.186 0 0 0 0 5.902 4.186 4.186 0 0 0 5.902 0L19.16 9.48a.835.835 0 0 0 0-1.18.835.835 0 0 0-1.18 0l-6.985 6.984a2.49 2.49 0 0 1-3.54 0 2.49 2.49 0 0 1 0-3.54l6.983-6.985a.835.835 0 0 0 0-1.18.84.84 0 0 0-.59-.245"
className="fill-foreground"
/>
</svg>
)}
MCP
<Button size="sm" variant="outline" className="relative z-10">
Add <IconPlus />
</Button>
)
const Content = (
<>
<figure
data-rehype-pretty-code-figure
className={cn(
"group relative mt-0",
!isMobile &&
"dark:bg-background dark:[&_[data-line]:not([data-highlighted-line]):before]:bg-background!"
)}
>
<CopyButton
value={jsonValue}
className="top-3 right-2"
tooltip="Copy Code"
/>
<div data-rehype-pretty-code-title>components.json</div>
<pre className="no-scrollbar min-w-0 overflow-x-auto px-4 py-3.5 outline-none has-[[data-highlighted-line]]:px-0 has-[[data-line-numbers]]:px-0 has-[[data-slot=tabs]]:p-0">
<code data-line-numbers data-language="json">
<span data-line>{"{"}</span>
<span data-line>{' "registries": {'}</span>
<span
data-line
data-highlighted-line
>{` "${registry.name}": "${registry.url}"`}</span>
<span data-line>{" }"}</span>
<span data-line>{"}"}</span>
</code>
</pre>
</figure>
</>
<Tabs
value={packageManager}
onValueChange={(value) => {
setConfig({
...config,
packageManager: value as "pnpm" | "npm" | "yarn" | "bun",
})
}}
className="gap-0 overflow-hidden rounded-lg border"
>
<div className="flex items-center gap-2 border-b p-2">
<TabsList className="*:data-[slot=tabs-trigger]:data-[state=active]:border-input h-auto rounded-none bg-transparent p-0 font-mono *:data-[slot=tabs-trigger]:border *:data-[slot=tabs-trigger]:border-transparent *:data-[slot=tabs-trigger]:pt-0.5 *:data-[slot=tabs-trigger]:shadow-none!">
<TabsTrigger value="pnpm">pnpm</TabsTrigger>
<TabsTrigger value="npm">npm</TabsTrigger>
<TabsTrigger value="yarn">yarn</TabsTrigger>
<TabsTrigger value="bun">bun</TabsTrigger>
</TabsList>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon-sm"
variant="ghost"
className="ml-auto size-7 rounded-lg"
onClick={handleCopy}
>
{hasCopied ? (
<IconCheck className="size-4" />
) : (
<IconCopy className="size-4" />
)}
<span className="sr-only">Copy command</span>
</Button>
</TooltipTrigger>
<TooltipContent>
{hasCopied ? "Copied!" : "Copy command"}
</TooltipContent>
</Tooltip>
</div>
{Object.entries(commands).map(([key, cmd]) => (
<TabsContent key={key} value={key} className="mt-0">
<div className="bg-surface text-surface-foreground px-3 py-3">
<div className="no-scrollbar overflow-x-auto">
<code className="font-mono text-sm whitespace-nowrap">{cmd}</code>
</div>
</div>
</TabsContent>
))}
</Tabs>
)
if (isMobile) {
@@ -108,20 +143,16 @@ export function DirectoryAddButton({
<DrawerTrigger asChild>{Trigger}</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Configure MCP</DrawerTitle>
<DrawerTitle>Add Registry</DrawerTitle>
<DrawerDescription>
Copy and paste the following code into your project&apos;s
components.json.
Run this command to add {registry.name} to your project.
</DrawerDescription>
</DrawerHeader>
<div className="px-6">{Content}</div>
<div className="px-4">{Content}</div>
<DrawerFooter>
<DrawerClose asChild>
<Button size="sm">Close</Button>
<Button size="sm">Done</Button>
</DrawerClose>
<Button size="sm" asChild variant="outline">
<Link href="/docs/mcp">Read the docs</Link>
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
@@ -131,22 +162,15 @@ export function DirectoryAddButton({
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{Trigger}</DialogTrigger>
<DialogContent
className="rounded-xl border-none bg-clip-padding shadow-2xl ring-4 ring-neutral-200/80 sm:max-w-[600px] dark:bg-neutral-900 dark:ring-neutral-800"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<DialogContent className="dialog-ring animate-none! rounded-xl sm:max-w-md">
<DialogHeader>
<DialogTitle>Configure MCP</DialogTitle>
<DialogTitle>Add Registry</DialogTitle>
<DialogDescription>
Copy and paste the following code into your project&apos;s
components.json.
Run this command to add {registry.name} to your project.
</DialogDescription>
</DialogHeader>
{Content}
<DialogFooter className="justify-between!">
<Button size="sm" asChild variant="ghost">
<Link href="/docs/mcp">Read the docs</Link>
</Button>
<DialogFooter>
<DialogClose asChild>
<Button size="sm">Done</Button>
</DialogClose>

View File

@@ -50,8 +50,10 @@ export function DirectoryList() {
href={getHomepageUrl(registry.homepage)}
target="_blank"
rel="noopener noreferrer external"
className="group flex items-center gap-1"
>
{registry.name}
{registry.name}{" "}
<IconArrowUpRight className="size-4 opacity-0 group-hover:opacity-100" />
</a>
</ItemTitle>
{registry.description && (
@@ -61,15 +63,6 @@ export function DirectoryList() {
)}
</ItemContent>
<ItemActions className="relative z-10 hidden self-start sm:flex">
<Button size="sm" variant="outline" asChild>
<a
href={getHomepageUrl(registry.homepage)}
target="_blank"
rel="noopener noreferrer external"
>
View <IconArrowUpRight />
</a>
</Button>
<DirectoryAddButton registry={registry} />
</ItemActions>
<ItemFooter className="justify-start pl-16 sm:hidden">

View File

@@ -10,17 +10,17 @@ These registries are built into the CLI with no additional configuration require
<Callout
type="warning"
icon={<TriangleAlertIcon />}
className="gap-2! border-amber-200 bg-amber-50 p-2 font-semibold dark:border-amber-800 dark:bg-amber-900 *:[svg]:translate-y-1"
className="gap-2! border-amber-200 bg-amber-50 p-2 font-semibold dark:border-amber-900 dark:bg-amber-950/80 *:[svg]:translate-y-1"
>
Community registries are maintained by third-party developers and are not
officially curated. Always review code on installation to ensure it meets your
security and quality standards.
</Callout>
<DirectoryList />
Don't see a registry? Learn how to [add it here](/docs/registry/registry-index).
<DirectoryList />
## Documentation
You can use the `shadcn` CLI to run your own code registry. Running your own registry allows you to distribute your custom components, hooks, pages, config, rules and other files to any project.

View File

@@ -11,9 +11,10 @@ You can see the full list at [https://ui.shadcn.com/r/registries.json](https://u
## Adding a Registry
You can open an issue to add a registry to the index by filling out the [registry directory issue template](https://github.com/shadcn-ui/ui/issues/new?template=registry_directory.yml).
1. Add your registry to [`apps/v4/registry/directory.json`](https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/directory.json)
2. Create a pull request to https://github.com/shadcn-ui/ui
Once you have submitted your issue, it will be validated and reviewed by the team.
Once you have submitted your request, it will be validated and reviewed by the team.
### Requirements

View File

@@ -19,6 +19,7 @@ const eventSchema = z.object({
"search_query",
"create_app",
"copy_create_share_url",
"copy_registry_add_command",
]),
// declare type AllowedPropertyValues = string | number | boolean | null
properties: z

View File

@@ -0,0 +1,137 @@
{
"@8bitcn": "https://8bitcn.com/r/{name}.json",
"@8starlabs-ui": "https://ui.8starlabs.com/r/{name}.json",
"@97cn": "https://97cn.itzik.co/r/{name}.json",
"@abstract": "https://build.abs.xyz/r/{name}/json",
"@abui": "https://abui.io/r/{name}.json",
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
"@aevr": "https://ui.aevr.space/r/{name}.json",
"@ai-blocks": "https://webllm.org/r/{name}.json",
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
"@algolia": "https://sitesearch.algolia.com/r/{name}.json",
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
"@aliimam": "https://aliimam.in/r/{name}.json",
"@animate-ui": "https://animate-ui.com/r/{name}.json",
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
"@basecn": "https://basecn.dev/r/{name}.json",
"@better-upload": "https://better-upload.com/r/{name}.json",
"@billingsdk": "https://billingsdk.com/r/{name}.json",
"@blocks": "https://blocks.so/r/{name}.json",
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
"@bundui": "https://bundui.io/r/{name}.json",
"@cardcn": "https://cardcn.dev/r/{name}.json",
"@chamaac": "https://chamaac.com/r/{name}.json",
"@clerk": "https://clerk.com/r/{name}.json",
"@coss": "https://coss.com/ui/r/{name}.json",
"@commercn": "https://commercn.com/r/{name}.json",
"@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json",
"@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json",
"@cult-ui": "https://cult-ui.com/r/{name}.json",
"@diceui": "https://diceui.com/r/{name}.json",
"@doras-ui": "https://ui.doras.to/r/{name}.json",
"@efferd": "https://efferd.com/r/{name}.json",
"@einui": "https://ui.eindev.ir/r/{name}.json",
"@eldoraui": "https://eldoraui.site/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
"@fancy": "https://fancycomponents.dev/r/{name}.json",
"@formcn": "https://formcn.dev/r/{name}.json",
"@gaia": "https://ui.heygaia.io/r/{name}.json",
"@glass-ui": "https://glass-ui.crenspire.com/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@hooks": "https://shadcn-hooks.vercel.app/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
"@kanpeki": "https://kanpeki.vercel.app/r/{name}.json",
"@kokonutui": "https://kokonutui.com/r/{name}.json",
"@lens-blocks": "https://lensblocks.com/r/{name}.json",
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
"@lucide-animated": "https://lucide-animated.com/r/{name}.json",
"@lytenyte": "https://www.1771technologies.com/r/{name}.json",
"@marmelab": "https://marmelab.com/shadcn-admin-kit/r/{name}.json",
"@magicui": "https://magicui.design/r/{name}.json",
"@magicui-pro": "https://pro.magicui.design/registry/{name}",
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
"@manifest": "https://ui.manifest.build/r/{name}.json",
"@mui-treasury": "https://mui-treasury.com/r/{name}.json",
"@nativeui": "https://nativeui.io/registry/{name}.json",
"@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json",
"@ncdai": "https://chanhdai.com/r/{name}.json",
"@neobrutalism": "https://www.neobrutalism.dev/r/{name}.json",
"@nuqs": "https://nuqs.dev/r/{name}.json",
"@optics": "https://optics.agusmayol.com.ar/r/{name}.json",
"@oui": "https://oui.mw10013.workers.dev/r/{name}.json",
"@paceui": "https://ui.paceui.com/r/{name}.json",
"@plate": "https://platejs.org/r/{name}.json",
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
"@prosekit": "https://prosekit.dev/r/{name}.json",
"@phucbm": "https://phucbm.com/r/{name}.json",
"@react-aria": "https://react-aria.adobe.com/registry/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@react-market": "https://www.react-market.com/get/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
"@reui": "https://reui.io/r/{name}.json",
"@rigidui": "https://rigidui.com/r/{name}.json",
"@roiui": "https://roiui.com/r/{name}.json",
"@solaceui": "https://www.solaceui.com/r/{name}.json",
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
"@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json",
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
"@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json",
"@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json",
"@simple-ai": "https://simple-ai.dev/r/{name}.json",
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
"@smoothui": "https://smoothui.dev/r/{name}.json",
"@spectrumui": "https://ui.spectrumhq.in/r/{name}.json",
"@supabase": "https://supabase.com/ui/r/{name}.json",
"@svgl": "https://svgl.app/r/{name}.json",
"@tailark": "https://tailark.com/r/{name}.json",
"@tailwind-admin": "https://tailwind-admin.com/r/{name}.json",
"@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json",
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
"@animbits": "https://animbits.dev/r/{name}.json",
"@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json",
"@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json",
"@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json",
"@pixelact-ui": "https://www.pixelactui.com/r/{name}.json",
"@zippystarter": "https://zippystarter.com/r/{name}.json",
"@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json",
"@ha-components": "https://hacomponents.keshuac.com/r/{name}.json",
"@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json",
"@utilcn": "https://utilcn.dev/r/{name}.json",
"@hextaui": "https://hextaui.com/r/{name}.json",
"@taki": "https://taki-ui.com/r/{name}.json",
"@square-ui": "https://square.lndev.me/registry/{name}.json",
"@moleculeui": "https://moleculeui.design/r/{name}.json",
"@uicapsule": "https://uicapsule.com/r/{name}.json",
"@uitripled": "https://ui.tripled.work/r/{name}.json",
"@ui-layouts": "https://ui-layouts.com/r/{name}.json",
"@pureui": "https://pure.kam-ui.com/r/{name}.json",
"@tour": "https://onboarding-tour.vercel.app/r/{name}.json",
"@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json",
"@heroicons-animated": "https://www.heroicons-animated.com/r/{name}.json",
"@icons-animated": "https://icons.lndev.me/r/{name}.json",
"@forgeui": "https://forgeui.in/r/{name}.json",
"@darx": "https://darshitdev.in/r/{name}.json",
"@gamifykit": "https://gamifykit.com/r/{name}.json",
"@beste-ui": "https://ui.beste.co/r/{name}.json",
"@tokenui": "https://www.tokenui.dev/r/{name}.json",
"@lumiui": "https://www.lumiui.dev/r/{name}.json",
"@uselayouts": "https://uselayouts.com/r/{name}.json",
"@joyco": "https://registry.joyco.studio/r/{name}.json",
"@gooseui": "https://gooseui.pro/r/{name}.json",
"@baselayer": "https://www.baselayer.dev/r/{name}.json",
"@jolyui": "https://www.jolyui.dev/r/{name}.json",
"@fab-ui": "https://fab-ui.com/r/{name}.json",
"@asanshay": "https://ds.asanshay.com/r/{name}.json",
"@headcodecms": "https://headcodecms.com/r/{name}.json",
"@typedora-ui": "https://typedora-ui.netlify.app/r/{name}.json",
"@agents-ui": "https://livekit.io/ui/r/{name}.json"
}

View File

@@ -1,137 +1,686 @@
{
"@8bitcn": "https://8bitcn.com/r/{name}.json",
"@8starlabs-ui": "https://ui.8starlabs.com/r/{name}.json",
"@97cn": "https://97cn.itzik.co/r/{name}.json",
"@abstract": "https://build.abs.xyz/r/{name}/json",
"@abui": "https://abui.io/r/{name}.json",
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
"@aevr": "https://ui.aevr.space/r/{name}.json",
"@ai-blocks": "https://webllm.org/r/{name}.json",
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
"@alexcarpenter": "https://ui.alexcarpenter.me/r/{name}.json",
"@algolia": "https://sitesearch.algolia.com/r/{name}.json",
"@alpine": "https://alpine-registry.vercel.app/r/{name}.json",
"@aliimam": "https://aliimam.in/r/{name}.json",
"@animate-ui": "https://animate-ui.com/r/{name}.json",
"@assistant-ui": "https://r.assistant-ui.com/{name}.json",
"@austin-ui": "https://austin-ui.netlify.app/r/{name}.json",
"@basecn": "https://basecn.dev/r/{name}.json",
"@better-upload": "https://better-upload.com/r/{name}.json",
"@billingsdk": "https://billingsdk.com/r/{name}.json",
"@blocks": "https://blocks.so/r/{name}.json",
"@bucharitesh": "https://bucharitesh.in/r/{name}.json",
"@bundui": "https://bundui.io/r/{name}.json",
"@cardcn": "https://cardcn.dev/r/{name}.json",
"@chamaac": "https://chamaac.com/r/{name}.json",
"@clerk": "https://clerk.com/r/{name}.json",
"@coss": "https://coss.com/ui/r/{name}.json",
"@commercn": "https://commercn.com/r/{name}.json",
"@chisom-ui": "https://chisom-ui.netlify.app/r/{name}.json",
"@creative-tim": "https://www.creative-tim.com/ui/r/{name}.json",
"@cult-ui": "https://cult-ui.com/r/{name}.json",
"@diceui": "https://diceui.com/r/{name}.json",
"@doras-ui": "https://ui.doras.to/r/{name}.json",
"@efferd": "https://efferd.com/r/{name}.json",
"@einui": "https://ui.eindev.ir/r/{name}.json",
"@eldoraui": "https://eldoraui.site/r/{name}.json",
"@elements": "https://tryelements.dev/r/{name}.json",
"@elevenlabs-ui": "https://ui.elevenlabs.io/r/{name}.json",
"@fancy": "https://fancycomponents.dev/r/{name}.json",
"@formcn": "https://formcn.dev/r/{name}.json",
"@gaia": "https://ui.heygaia.io/r/{name}.json",
"@glass-ui": "https://glass-ui.crenspire.com/r/{name}.json",
"@heseui": "https://www.heseui.com/r/{name}.json",
"@hooks": "https://shadcn-hooks.vercel.app/r/{name}.json",
"@intentui": "https://intentui.com/r/{name}",
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json",
"@kanpeki": "https://kanpeki.vercel.app/r/{name}.json",
"@kokonutui": "https://kokonutui.com/r/{name}.json",
"@lens-blocks": "https://lensblocks.com/r/{name}.json",
"@limeplay": "https://limeplay.winoffrg.dev/r/{name}.json",
"@lucide-animated": "https://lucide-animated.com/r/{name}.json",
"@lytenyte": "https://www.1771technologies.com/r/{name}.json",
"@marmelab": "https://marmelab.com/shadcn-admin-kit/r/{name}.json",
"@magicui": "https://magicui.design/r/{name}.json",
"@magicui-pro": "https://pro.magicui.design/registry/{name}",
"@motion-primitives": "https://motion-primitives.com/c/{name}.json",
"@manifest": "https://ui.manifest.build/r/{name}.json",
"@mui-treasury": "https://mui-treasury.com/r/{name}.json",
"@nativeui": "https://nativeui.io/registry/{name}.json",
"@nexus-elements": "https://elements.nexus.availproject.org/r/{name}.json",
"@ncdai": "https://chanhdai.com/r/{name}.json",
"@neobrutalism": "https://www.neobrutalism.dev/r/{name}.json",
"@nuqs": "https://nuqs.dev/r/{name}.json",
"@optics": "https://optics.agusmayol.com.ar/r/{name}.json",
"@oui": "https://oui.mw10013.workers.dev/r/{name}.json",
"@paceui": "https://ui.paceui.com/r/{name}.json",
"@plate": "https://platejs.org/r/{name}.json",
"@prompt-kit": "https://prompt-kit.com/c/{name}.json",
"@prosekit": "https://prosekit.dev/r/{name}.json",
"@phucbm": "https://phucbm.com/r/{name}.json",
"@react-aria": "https://react-aria.adobe.com/registry/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json",
"@react-market": "https://www.react-market.com/get/{name}.json",
"@retroui": "https://retroui.dev/r/{name}.json",
"@reui": "https://reui.io/r/{name}.json",
"@rigidui": "https://rigidui.com/r/{name}.json",
"@roiui": "https://roiui.com/r/{name}.json",
"@solaceui": "https://www.solaceui.com/r/{name}.json",
"@scrollxui": "https://www.scrollxui.dev/registry/{name}.json",
"@systaliko-ui": "https://systaliko-ui.vercel.app/r/{name}.json",
"@shadcn-editor": "https://shadcn-editor.vercel.app/r/{name}.json",
"@shadcn-map": "http://shadcn-map.vercel.app/r/{name}.json",
"@shadcn-studio": "https://shadcnstudio.com/r/{name}.json",
"@shadcnblocks": "https://shadcnblocks.com/r/{name}.json",
"@shadcnui-blocks": "https://shadcnui-blocks.com/r/{name}.json",
"@shadcraft": "https://shadcraft-free.vercel.app/r/{name}.json",
"@simple-ai": "https://simple-ai.dev/r/{name}.json",
"@skiper-ui": "https://skiper-ui.com/registry/{name}.json",
"@skyr": "https://ui-play.skyroc.me/r/{name}.json",
"@smoothui": "https://smoothui.dev/r/{name}.json",
"@spectrumui": "https://ui.spectrumhq.in/r/{name}.json",
"@supabase": "https://supabase.com/ui/r/{name}.json",
"@svgl": "https://svgl.app/r/{name}.json",
"@tailark": "https://tailark.com/r/{name}.json",
"@tailwind-admin": "https://tailwind-admin.com/r/{name}.json",
"@tailwind-builder": "https://tailwindbuilder.ai/r/{name}.json",
"@tweakcn": "https://tweakcn.com/r/themes/{name}.json",
"@wds": "https://wds-shadcn-registry.netlify.app/r/{name}.json",
"@animbits": "https://animbits.dev/r/{name}.json",
"@wandry-ui": "https://ui.wandry.com.ua/r/{name}.json",
"@wigggle-ui": "https://wigggle-ui.vercel.app/r/{name}.json",
"@paykit-sdk": "https://www.usepaykit.dev/r/{name}.json",
"@pixelact-ui": "https://www.pixelactui.com/r/{name}.json",
"@zippystarter": "https://zippystarter.com/r/{name}.json",
"@shadcndesign": "https://shadcndesign-free.vercel.app/r/{name}.json",
"@ha-components": "https://hacomponents.keshuac.com/r/{name}.json",
"@shadix-ui": "https://shadix-ui.vercel.app/r/{name}.json",
"@utilcn": "https://utilcn.dev/r/{name}.json",
"@hextaui": "https://hextaui.com/r/{name}.json",
"@taki": "https://taki-ui.com/r/{name}.json",
"@square-ui": "https://square.lndev.me/registry/{name}.json",
"@moleculeui": "https://moleculeui.design/r/{name}.json",
"@uicapsule": "https://uicapsule.com/r/{name}.json",
"@uitripled": "https://ui.tripled.work/r/{name}.json",
"@ui-layouts": "https://ui-layouts.com/r/{name}.json",
"@pureui": "https://pure.kam-ui.com/r/{name}.json",
"@tour": "https://onboarding-tour.vercel.app/r/{name}.json",
"@tb-blocks": "https://tailwindbuilder.ai/r/blocks/{name}.json",
"@heroicons-animated": "https://www.heroicons-animated.com/r/{name}.json",
"@icons-animated": "https://icons.lndev.me/r/{name}.json",
"@forgeui": "https://forgeui.in/r/{name}.json",
"@darx": "https://darshitdev.in/r/{name}.json",
"@gamifykit": "https://gamifykit.com/r/{name}.json",
"@beste-ui": "https://ui.beste.co/r/{name}.json",
"@tokenui": "https://www.tokenui.dev/r/{name}.json",
"@lumiui": "https://www.lumiui.dev/r/{name}.json",
"@uselayouts": "https://uselayouts.com/r/{name}.json",
"@joyco": "https://registry.joyco.studio/r/{name}.json",
"@gooseui": "https://gooseui.pro/r/{name}.json",
"@baselayer": "https://www.baselayer.dev/r/{name}.json",
"@jolyui": "https://www.jolyui.dev/r/{name}.json",
"@fab-ui": "https://fab-ui.com/r/{name}.json",
"@asanshay": "https://ds.asanshay.com/r/{name}.json",
"@headcodecms": "https://headcodecms.com/r/{name}.json",
"@typedora-ui": "https://typedora-ui.netlify.app/r/{name}.json",
"@agents-ui": "https://livekit.io/ui/r/{name}.json"
}
[
{
"name": "@8bitcn",
"homepage": "https://www.8bitcn.com",
"url": "https://www.8bitcn.com/r/{name}.json",
"description": "A set of 8-bit styled retro components. Works with your favorite frameworks. Open Source. Open Code."
},
{
"name": "@8starlabs-ui",
"homepage": "https://ui.8starlabs.com",
"url": "https://ui.8starlabs.com/r/{name}.json",
"description": "A set of beautifully designed components designed for developers who want niche, high-utility UI elements that you won't find in standard libraries."
},
{
"name": "@abui",
"homepage": "https://abui.io",
"url": "https://abui.io/r/{name}.json",
"description": "A shadcn-compatible registry of reusable components, blocks, and utilities conforming to Vercel's components.build specification"
},
{
"name": "@abstract",
"homepage": "https://build.abs.xyz",
"url": "https://build.abs.xyz/r/{name}/json",
"description": "A collection of React components for the most common crypto patterns"
},
{
"name": "@aceternity",
"homepage": "https://ui.aceternity.com",
"url": "https://ui.aceternity.com/registry/{name}.json",
"description": "A modern component library built with Tailwind CSS and Motion for React, Aceternity UI contains unique and interactive components that can make your landing pages look 100x better."
},
{
"name": "@agents-ui",
"homepage": "https://livekit.io/ui",
"url": "https://livekit.io/ui/r/{name}.json",
"description": "This is a shadcn/ui component registry that distributes copy-paste React components for building LiveKit AI Agent interfaces."
},
{
"name": "@aevr",
"homepage": "https://ui.aevr.space",
"url": "https://ui.aevr.space/r/{name}.json",
"description": "A small collection of focused, productionready components and primitives for React/Next.js projects—built on shadcn/ui and complementary libraries."
},
{
"name": "@ai-blocks",
"homepage": "https://webllm.org/blocks",
"url": "https://webllm.org/r/{name}.json",
"description": "AI components for the web. No server. No API keys. Built on WebLLM."
},
{
"name": "@ai-elements",
"homepage": "https://ai-sdk.dev/elements",
"url": "https://registry.ai-sdk.dev/{name}.json",
"description": "Pre-built components like conversations, messages and more to help you build AI-native applications faster."
},
{
"name": "@algolia",
"homepage": "https://sitesearch.algolia.com",
"url": "https://sitesearch.algolia.com/r/{name}.json",
"description": "Enterprises and developers use Algolia's AI search infrastructure to understand users and show them what they're looking for."
},
{
"name": "@aliimam",
"homepage": "https://aliimam.in",
"url": "https://aliimam.in/r/{name}.json",
"description": "I create digital experiences that connect and inspire. I build apps, websites, brands, and products end-to-end."
},
{
"name": "@animate-ui",
"homepage": "https://animate-ui.com",
"url": "https://animate-ui.com/r/{name}.json",
"description": "A fully animated, open-source React component distribution. Browse a list of animated primitives, components and icons you can install and use in your projects."
},
{
"name": "@assistant-ui",
"homepage": "https://www.assistant-ui.com",
"url": "https://r.assistant-ui.com/{name}.json",
"description": "Radix-style React primitives for AI chat with adapters for AI SDK, LangGraph, Mastra, and custom backends."
},
{
"name": "@better-upload",
"homepage": "https://better-upload.com",
"url": "https://better-upload.com/r/{name}.json",
"description": "Simple and easy file uploads for React. Upload directly to any S3-compatible service with minimal setup."
},
{
"name": "@basecn",
"homepage": "https://basecn.dev",
"url": "https://basecn.dev/r/{name}",
"description": "Beautifully crafted shadcn/ui components powered by Base UI"
},
{
"name": "@billingsdk",
"homepage": "https://billingsdk.com",
"url": "https://billingsdk.com/r/{name}.json",
"description": "BillingSDK is an open-source React and Next.js component library for SaaS billing and payments. It offers ready-to-use, customizable components for subscriptions, invoices, usage-based pricing and billing - fully compatible with Dodo Payments and Stripe."
},
{
"name": "@blocks",
"homepage": "https://blocks.so",
"url": "https://blocks.so/r/{name}.json",
"description": "A set of clean, modern application building blocks for you in your applications. Free and Open Source"
},
{
"name": "@bundui",
"homepage": "https://bundui.io",
"url": "https://bundui.io/r/{name}.json",
"description": "A collection of 150+ handcrafted UI components built with Tailwind CSS and shadcn/ui, covering marketing, e-commerce, dashboards, real estate, and more."
},
{
"name": "@cardcn",
"homepage": "https://cardcn.dev",
"url": "https://cardcn.dev/r/{name}.json",
"description": "A set of beautifully-designed shadcn card components"
},
{
"name": "@chamaac",
"homepage": "https://chamaac.com",
"url": "https://chamaac.com/r/{name}.json",
"description": "A collection of beautiful, animated components to elevate your web projects instantly."
},
{
"name": "@clerk",
"homepage": "https://clerk.com/docs/guides/development/shadcn-cli",
"url": "https://clerk.com/r/{name}.json",
"description": "The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and The Modern Web."
},
{
"name": "@commercn",
"homepage": "https://commercn.com",
"url": "https://commercn.com/r/{name}.json",
"description": "Shadcn UI Blocks for Ecommerce websites"
},
{
"name": "@coss",
"homepage": "https://coss.com/ui",
"url": "https://coss.com/ui/r/{name}.json",
"description": "A new, modern UI component library built on top of Base UI. Built for developers and AI."
},
{
"name": "@creative-tim",
"homepage": "https://www.creative-tim.com/ui",
"url": "https://www.creative-tim.com/ui/r/{name}.json",
"description": "A collection of open-source UI components, blocks and AI Agents. Integrate them in v0, Lovable, Claude or in your application."
},
{
"name": "@cult-ui",
"homepage": "https://www.cult-ui.com",
"url": "https://cult-ui.com/r/{name}.json",
"description": "Cult UI is a rare, curated set of shadcn-compatible, headless and composable components—tastefully animated with Framer Motion."
},
{
"name": "@diceui",
"homepage": "https://www.diceui.com/",
"url": "https://diceui.com/r/{name}.json",
"description": "Accessible shadcn/ui components built with React, TypeScript, and Tailwind CSS. Copy-paste ready, and customizable."
},
{
"name": "@doras-ui",
"homepage": "https://ui.doras.to/",
"url": "https://ui.doras.to/r/{name}.json",
"description": "A collection of beautiful, reusable component blocks built with React"
},
{
"name": "@elements",
"homepage": "https://www.tryelements.dev",
"url": "https://www.tryelements.dev/r/registry.json",
"description": "Full-stack shadcn/ui components that go beyond UI. Add auth, monetization, uploads, and AI to your app in seconds."
},
{
"name": "@elevenlabs-ui",
"homepage": "https://ui.elevenlabs.io",
"url": "https://ui.elevenlabs.io/r/{name}.json",
"description": "A collection of Open Source agent and audio components that you can customize and extend."
},
{
"name": "@efferd",
"homepage": "https://efferd.com/",
"url": "https://efferd.com/r/registry.json",
"description": "A collection of beautifully crafted Shadcn/UI blocks, designed to help developers build modern websites with ease."
},
{
"name": "@einui",
"homepage": "https://ui.eindev.ir",
"url": "https://ui.eindev.ir/r/{name}.json",
"description": "Beautiful, responsive Shadcn components with frosted glass morphism. Built for modern web applications with full dark mode support."
},
{
"name": "@eldoraui",
"homepage": "https://eldoraui.site",
"url": "https://eldoraui.site/r/{name}.json",
"description": "An open-source, modern UI component library for React, built with TypeScript, Tailwind CSS, and Framer Motion. Eldora UI offers beautifully crafted, reusable components designed for performance and elegance."
},
{
"name": "@formcn",
"homepage": "https://formcn.dev",
"url": "https://formcn.dev/r/{name}.json",
"description": "Build production-ready forms with a few clicks using shadcn components and modern tools."
},
{
"name": "@gaia",
"homepage": "https://ui.heygaia.io",
"url": "https://ui.heygaia.io/r/{name}.json",
"description": "Production-ready UI components designed for building beautiful AI assistants and conversational interfaces, from the team behind GAIA."
},
{
"name": "@glass-ui",
"homepage": "https://glass-ui.crenspire.com",
"url": "https://glass-ui.crenspire.com/r/{name}.json",
"description": "A shadcn-ui compatible registry distributing 40+ glassmorphic React/TypeScript components with Apple-inspired design. Components include enhanced visual effects (glow, shimmer, ripple), theme support, and customizable glassmorphism styling."
},
{
"name": "@ha-components",
"homepage": "https://hacomponents.keshuac.com",
"url": "https://hacomponents.keshuac.com/r/{name}.json",
"description": "A collection of customisable components to build Home Assistant dashboards."
},
{
"name": "@hextaui",
"homepage": "https://hextaui.com",
"url": "https://hextaui.com/r/{name}.json",
"description": "Ready-to-use foundation components/blocks built on top of shadcn/ui."
},
{
"name": "@hooks",
"homepage": "https://shadcn-hooks.vercel.app",
"url": "https://shadcn-hooks.vercel.app/r/{name}.json",
"description": "A comprehensive React Hooks Collection built with Shadcn."
},
{
"name": "@intentui",
"homepage": "https://intentui.com",
"url": "https://intentui.com/r/{name}",
"description": "Accessible React component library to copy, customize, and own your UI."
},
{
"name": "@kibo-ui",
"homepage": "https://www.kibo-ui.com/",
"url": "https://www.kibo-ui.com/r/{name}.json",
"description": "Kibo UI is a custom registry of composable, accessible and open source components designed for use with shadcn/ui."
},
{
"name": "@kanpeki",
"homepage": "https://kanpeki.vercel.app",
"url": "https://kanpeki.vercel.app/r/{name}.json",
"description": "A set of perfect-designed components built on top of React Aria and Motion."
},
{
"name": "@kokonutui",
"homepage": "https://kokonutui.com",
"url": "https://kokonutui.com/r/{name}.json",
"description": "Collection of stunning components built with Tailwind CSS, shadcn/ui and Motion to use on your websites."
},
{
"name": "@lens-blocks",
"homepage": "https://lensblocks.com",
"url": "https://lensblocks.com/r/{name}.json",
"description": "A collection of social media components for use with Lens Social Protocol."
},
{
"name": "@limeplay",
"homepage": "https://limeplay.winoffrg.dev",
"url": "https://limeplay.winoffrg.dev/r/{name}.json",
"description": "Modern UI Library for building media players in React. Powered by Shaka Player."
},
{
"name": "@lucide-animated",
"homepage": "https://lucide-animated.com",
"url": "https://lucide-animated.com/r/{name}.json",
"description": "An open-source collection of smooth animated lucide icons for your projects"
},
{
"name": "@lytenyte",
"homepage": "https://www.1771technologies.com",
"url": "https://www.1771technologies.com/r/{name}.json",
"description": "LyteNyte Grid is a high performance, light weight, headless, React data grid. Our registry provides LyteNyte Grid themed using Tailwind and the Shadcn theme variables."
},
{
"name": "@magicui",
"homepage": "https://magicui.design",
"url": "https://magicui.design/r/{name}",
"description": "UI Library for Design Engineers. 150+ free and open-source animated components and effects built with React, Typescript, Tailwind CSS, and Motion. Perfect companion for shadcn/ui."
},
{
"name": "@manifest",
"homepage": "https://ui.manifest.build",
"url": "https://ui.manifest.build/r/{name}.json",
"description": "Agentic UI toolkit for building MCP Apps. Open-source components and blocks ready to use within your chat app."
},
{
"name": "@mui-treasury",
"homepage": "https://www.mui-treasury.com",
"url": "https://mui-treasury.com/r/{name}.json",
"description": "A collection of hand-crafted interfaces built on top of MUI components"
},
{
"name": "@moleculeui",
"homepage": "https://www.moleculeui.design/",
"url": "https://www.moleculeui.design/r/{name}.json",
"description": "A modern React component library focused on intuitive interactions and seamless user experiences."
},
{
"name": "@motion-primitives",
"homepage": "https://www.motion-primitives.com",
"url": "https://motion-primitives.com/c/registry.json",
"description": "Beautifully designed motions components. Easy copy-paste. Customizable. Open Source. Built for engineers and designers."
},
{
"name": "@ncdai",
"homepage": "https://chanhdai.com/components",
"url": "https://chanhdai.com/r/{name}.json",
"description": "A collection of reusable components."
},
{
"name": "@nuqs",
"homepage": "https://nuqs.dev/registry",
"url": "https://nuqs.dev/r/{name}.json",
"description": "Custom parsers, adapters and utilities from the community for type-safe URL state management."
},
{
"name": "@neobrutalism",
"homepage": "https://www.neobrutalism.dev",
"url": "https://www.neobrutalism.dev/r/{name}.json",
"description": "A collection of neobrutalism-styled components based on shadcn/ui"
},
{
"name": "@nexus-elements",
"homepage": "https://elements.nexus.availproject.org/docs/view-components",
"url": "https://elements.nexus.availproject.org/r/{name}.json",
"description": "Ready-made React components for almost any use case. Use as is or customise and go to market fast"
},
{
"name": "@optics",
"homepage": "https://optics.agusmayol.com.ar",
"url": "https://optics.agusmayol.com.ar/r/{name}.json",
"description": "A design system that distributes re-styled components, utilities, and hooks ready to use."
},
{
"name": "@oui",
"homepage": "https://oui.mw10013.workers.dev",
"url": "https://oui.mw10013.workers.dev/r/{name}.json",
"description": "React Aria Components with shadcn characteristics.Copy-and-paste react aria components that run side-by-side with shadcn components."
},
{
"name": "@paceui",
"homepage": "https://ui.paceui.com",
"url": "https://ui.paceui.com/r/{name}.json",
"description": "Animated components and building blocks built for smooth interaction and rich detail. Copy, customise, and create without the extra setup."
},
{
"name": "@paykit-sdk",
"homepage": "https://www.usepaykit.dev",
"url": "https://www.usepaykit.dev/r/{name}.json",
"description": "Unified payments SDK for builders — handle checkout, billing, and webhooks across Stripe, PayPal, Adyen, and regional gateways with a single integration."
},
{
"name": "@plate",
"homepage": "https://platejs.org",
"url": "https://platejs.org/r/{name}.json",
"description": "AI-powered rich text editor for React."
},
{
"name": "@prompt-kit",
"homepage": "https://www.prompt-kit.com",
"url": "https://www.prompt-kit.com/c/registry.json",
"description": "Core building blocks for AI apps. High-quality, accessible, and customizable components for AI interfaces."
},
{
"name": "@prosekit",
"homepage": "https://prosekit.dev",
"url": "https://prosekit.dev/r/{name}.json",
"description": "Powerful and flexible rich text editor for React, Vue, Preact, Svelte, and SolidJS."
},
{
"name": "@phucbm",
"homepage": "https://phucbm.com/components",
"url": "https://phucbm.com/r/{name}.json",
"description": "A collection of modern React UI components with GSAP animations."
},
{
"name": "@react-aria",
"homepage": "https://react-aria.adobe.com",
"url": "https://react-aria.adobe.com/registry/{name}.json",
"description": "Customizable Tailwind and Vanilla CSS components with adaptive interactions, top-tier accessibility, and internationalization."
},
{
"name": "@react-bits",
"homepage": "https://reactbits.dev",
"url": "https://reactbits.dev/r/{name}.json",
"description": "A large collection of animated, interactive & fully customizable React components for building memorable websites. From smooth text animations all the way to eye-catching backgrounds, you can find it here."
},
{
"name": "@retroui",
"homepage": "https://retroui.dev",
"url": "https://retroui.dev/r/{name}.json",
"description": "A Neobrutalism styled React + TailwindCSS UI library for building bold, modern web apps. Perfect for any project using Shadcn/ui."
},
{
"name": "@reui",
"homepage": "https://reui.io",
"url": "https://reui.io/r/{name}.json",
"description": "Open-source collection of UI components and animated effects built with React, Typescript, Tailwind CSS, and Motion. Pairs beautifully with shadcn/ui."
},
{
"name": "@scrollxui",
"homepage": "https://www.scrollxui.dev",
"url": "https://www.scrollxui.dev/registry/{name}.json",
"description": "ScrollX UI is an open-source React and shadcn-compatible component library for animated, interactive, and customizable user interfaces. It offers motion-driven components that blend seamlessly with modern ShadCN setups."
},
{
"name": "@square-ui",
"homepage": "https://square.lndev.me",
"url": "https://square.lndev.me/registry/{name}.json",
"description": "Collection of beautifully crafted open-source layouts UI built with shadcn/ui."
},
{
"name": "@systaliko-ui",
"homepage": "https://systaliko-ui.vercel.app",
"url": "https://systaliko-ui.vercel.app/r/{name}.json",
"description": "UI component library, Designed for flexibility, built for customization, and crafted to scale across variants and use cases."
},
{
"name": "@roiui",
"homepage": "https://roiui.com",
"url": "https://roiui.com/r/{name}.json",
"description": "Roi UI is a library that offers UI components and blocks built with Base UI primitives. Some blocks and components use motion (framer). Everything is open-source and will be forever."
},
{
"name": "@solaceui",
"homepage": "https://www.solaceui.com",
"url": "https://www.solaceui.com/r/{name}.json",
"description": "Production-ready and tastefully crafted sections, animated components, and full-page templates for Next.js, Tailwind CSS & Motion"
},
{
"name": "@shadcnblocks",
"homepage": "https://shadcnblocks.com",
"url": "https://shadcnblocks.com/r/{name}.json",
"description": "A registry with hundreds of extra blocks for shadcn ui."
},
{
"name": "@shadcndesign",
"homepage": "https://www.shadcndesign.com",
"url": "https://shadcndesign-free.vercel.app/r/{name}.json",
"description": "A growing collection of high-quality blocks and themes for shadcn/ui."
},
{
"name": "@shadcn-map",
"homepage": "https://shadcn-map.vercel.app",
"url": "http://shadcn-map.vercel.app/r/{name}.json",
"description": "A map component for shadcn/ui. Built with Leaflet and React Leaflet."
},
{
"name": "@shadcn-studio",
"homepage": "https://shadcnstudio.com",
"url": "https://shadcnstudio.com/r/{name}.json",
"description": "An open-source set of shadcn/ui components, blocks, and templates with a powerful theme generator."
},
{
"name": "@shadcn-editor",
"homepage": "https://shadcn-editor.vercel.app",
"url": "https://shadcn-editor.vercel.app/r/{name}.json",
"description": "Accessible, Customizable, Rich Text Editor. Made with Lexical and Shadcn/UI. Open Source. Open Code."
},
{
"name": "@shadcnui-blocks",
"homepage": "https://shadcnui-blocks.com",
"url": "https://shadcnui-blocks.com/r/{name}.json",
"description": "A collection of premium, production-ready shadcn/ui blocks, components and templates."
},
{
"name": "@shadcraft",
"homepage": "https://shadcraft-free.vercel.app",
"url": "https://shadcraft-free.vercel.app/r/{name}.json",
"description": "A collection of polished shadcn/ui components and marketing blocks built to production standards. Fast to use, easy to extend, and ready for any modern web project."
},
{
"name": "@smoothui",
"homepage": "https://smoothui.dev",
"url": "https://smoothui.dev/r/{name}.json",
"description": "A collection of beautifully crafted motion components built with React, Framer Motion, and TailwindCSS. Designed to elevate microinteractions, each component focuses on smooth animations, subtle feedback, and delightful UX. Perfect for designers and developers who want to add refined motion to their interfaces — copy, paste, and make your UI come alive."
},
{
"name": "@spectrumui",
"homepage": "https://ui.spectrumhq.in",
"url": "https://ui.spectrumhq.in/r/{name}.json",
"description": "A modern component library built with shadcn/ui and Tailwind CSS. Spectrum UI offers elegant, responsive components and smooth animations designed for high-quality interfaces."
},
{
"name": "@supabase",
"homepage": "https://supabase.com/ui",
"url": "https://supabase.com/ui/r/{name}.json",
"description": "A collection of React components and blocks built on the shadcn/ui library that connect your front-end to your Supabase back-end via a single command."
},
{
"name": "@svgl",
"homepage": "https://svgl.app",
"url": "https://svgl.app/r/{name}.json",
"description": "A beautiful library with SVG logos."
},
{
"name": "@tailark",
"homepage": "https://tailark.com",
"url": "https://tailark.com/r/{name}.json",
"description": "Shadcn blocks designed for building modern marketing websites."
},
{
"name": "@taki",
"homepage": "https://taki-ui.com",
"url": "https://taki-ui.com/r/{name}.json",
"description": "Beautifully designed, accessible components that you can copy and paste into your apps. Made with React Aria Components and Shadcn tokens."
},
{
"name": "@tour",
"homepage": "https://onboarding-tour.vercel.app",
"url": "https://onboarding-tour.vercel.app/r/{name}.json",
"description": "A component for building onboarding tours. Designed to integrate with shadcn/ui."
},
{
"name": "@uitripled",
"homepage": "https://ui.tripled.work",
"url": "https://ui.tripled.work/r/{name}.json",
"description": "An open-source, Production-ready UI components and blocks powered by shadcn/ui and Framer Motion"
},
{
"name": "@utilcn",
"homepage": "https://utilcn.dev",
"url": "https://utilcn.dev/r/{name}.json",
"description": "Fullstack registry items to start those big features. Utilcn has ChatGPT Apps, file uploading (with progress bars) and downloading, and a way to make your env vars typesafe on the backend."
},
{
"name": "@wandry-ui",
"homepage": "http://ui.wandry.com.ua/",
"url": "https://ui.wandry.com.ua/r/{name}.json",
"description": "A set of open source fully controlled React Inertia form elements"
},
{
"name": "@wigggle-ui",
"homepage": "https://wigggle-ui.vercel.app",
"url": "https://wigggle-ui.vercel.app/r/{name}.json",
"description": "A beautiful collection of copy-and-paste widgets for your next project."
},
{
"name": "@zippystarter",
"homepage": "https://zippystarter.com",
"url": "https://zippystarter.com/r/{name}.json",
"description": "Expertly crafted blocks, components & themes for shadcn/ui."
},
{
"name": "@uicapsule",
"homepage": "https://uicapsule.com",
"url": "https://uicapsule.com/r/{name}.json",
"description": "A curated collection of components that spark joy. Featuring interactive concepts, design experiments, and components in the intersection of AI/UI."
},
{
"name": "@ui-layouts",
"homepage": "https://ui-layouts.com/",
"url": "https://ui-layouts.com/r/{name}.json",
"description": "UI Layouts offers components, effects, design tools, and ready-made blocks that make building modern interfaces more efficient—built with React, Next.js, Tailwind CSS, and shadcn/ui."
},
{
"name": "@pureui",
"homepage": "https://pure.kam-ui.com/",
"url": "https://pure.kam-ui.com/r/{name}.json",
"description": "Pure UI is a curated collection of refined, animated, and accessible components built with Base UI, Tailwind CSS, Motion, and other high-quality open source libraries."
},
{
"name": "@tailwind-builder",
"homepage": "https://tailwindbuilder.ai/",
"url": "https://tailwindbuilder.ai/r/{name}.json",
"description": "Tailwind Builder is a collection of free ui blocks and components and provide ai tools to generate production-ready forms, tables, and charts in seconds. Built with React, Next.js, Tailwind & ShadCN."
},
{
"name": "@tailwind-admin",
"homepage": "https://tailwind-admin.com/",
"url": "https://tailwind-admin.com/r/{name}.json",
"description": "Tailwind Builder provides free tailwind admin dashboard templates, components and ui-blocks built with React, Next.js, Tailwind CSS, and shadcn/ui to help you build admin panels quickly and efficiently."
},
{
"name": "@forgeui",
"homepage": "https://forgeui.in/",
"url": "https://forgeui.in/r/{name}.json",
"description": "Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source."
},
{
"name": "@skiper-ui",
"homepage": "https://skiper-ui.com/",
"url": "https://skiper-ui.com/registry/{name}.json",
"description": "Brand new uncommon components for your Next.js project. Use with ease through shadcn CLI 3.0, featuring fast-growing components and collections that are easy to edit and use."
},
{
"name": "@animbits",
"homepage": "https://animbits.dev",
"url": "https://animbits.dev/r/{name}.json",
"description": "AnimBits is a collection animated UI components for React that use Framer Motion. The components provided include buttons, cards, text, icons, lists, loaders, and page transitions, animation hooks all of which have general-purpose effects that are not flashy and easy on the eyes, making them easy to use."
},
{
"name": "@icons-animated",
"homepage": "https://icons.lndev.me",
"url": "https://icons.lndev.me/r/{name}.json",
"description": "An open-source library of meticulously animated icons (Tabler, Phosphor, and more) for your projects, inspired by lucide-animated.com"
},
{
"name": "@heroicons-animated",
"homepage": "https://www.heroicons-animated.com/",
"url": "https://www.heroicons-animated.com/r/{name}.json",
"description": "An open-source collection of 316 beautifully animated heroicons for your projects."
},
{
"name": "@darx",
"homepage": "https://darshitdev.in/arts",
"url": "https://darshitdev.in/r/{name}.json",
"description": "Magic 3D Tabs component featuring mouse-interactive 3D rotation, floating particles background effect, and smooth spring animations."
},
{
"name": "@beste-ui",
"homepage": "https://ui.beste.co",
"url": "https://ui.beste.co/r/registry.json",
"description": "Production-ready UI blocks for landing pages, dashboards, and web apps."
},
{
"name": "@tokenui",
"homepage": "https://www.tokenui.dev",
"url": "https://www.tokenui.dev/r/{name}.json",
"description": "Beautiful, interactive documentation components for your design tokens following industry standards."
},
{
"name": "@lumiui",
"homepage": "https://www.lumiui.dev",
"url": "https://www.lumiui.dev/r/{name}.json",
"description": "Composable React components powered by Base UI and Tailwind CSS — Build fast, customize everything."
},
{
"name": "@uselayouts",
"homepage": "https://uselayouts.com",
"url": "https://uselayouts.com/r/{name}.json",
"description": "A collection of premium animated React components and micro-interactions built with Motion for building fluid, professional interfaces."
},
{
"name": "@joyco",
"homepage": "https://registry.joyco.studio",
"url": "https://registry.joyco.studio/r/{name}.json",
"description": "Components including MobileMenu, ScrollArea with gradients, Chat UI, HLSVideoPlayer, and Marquee."
},
{
"name": "@gooseui",
"homepage": "https://gooseui.pro",
"url": "https://gooseui.pro/r/{name}.json",
"description": "Open source component library with animated components, beautiful effects, and custom toast notifications. Built with Radix UI and Tailwind CSS."
},
{
"name": "@baselayer",
"homepage": "https://www.baselayer.dev",
"url": "https://www.baselayer.dev/r/{name}.json",
"description": "A collection of components built on React Aria, Tailwind CSS, and tailwind-variants."
},
{
"name": "@jolyui",
"homepage": "https://www.jolyui.dev",
"url": "https://www.jolyui.dev/r/{name}.json",
"description": "JolyUI is a modern React component library built with TypeScript and Tailwind CSS."
},
{
"name": "@fab-ui",
"homepage": "https://fab-ui.com",
"url": "https://fab-ui.com/r/{name}.json",
"description": "A collection of beautifully designed UI components for building modern web applications."
},
{
"name": "@asanshay",
"homepage": "https://ds.asanshay.com",
"url": "https://ds.asanshay.com/r/{name}.json",
"description": "Clean, beautiful, and simple UI primitives and AI elements."
},
{
"name": "@headcodecms",
"homepage": "https://headcodecms.com",
"url": "https://headcodecms.com/r/{name}.json",
"description": "A Minimalistic Web CMS for Next.js, optimized for Cache Components."
},
{
"name": "@typedora-ui",
"homepage": "https://typedora-ui.netlify.app",
"url": "https://typedora-ui.netlify.app/r/{name}.json",
"description": "Typedora UI is a next-generation extension layer for shadcn/ui, designed to bring full type-safety to your UI components."
}
]

View File

@@ -54,6 +54,9 @@ try {
console.log("\n⚙ Building public/r/config.json...")
await buildConfig()
console.log("\n📋 Building public/r/registries.json...")
await buildRegistriesJson()
// Clean up intermediate files and generated base directories.
console.log("\n🧹 Cleaning up...")
await cleanUp(stylesToBuild)
@@ -451,3 +454,42 @@ async function buildConfig() {
})
})
}
// Build public/r/registries.json from registry/directory.json.
// This generates a slim version without logos for CLI consumption.
async function buildRegistriesJson() {
// Read the source directory.json.
const directoryPath = path.join(process.cwd(), "registry/directory.json")
const directoryContent = await fs.readFile(directoryPath, "utf8")
const directory = JSON.parse(directoryContent) as Array<{
name: string
homepage?: string
url: string
description?: string
featured?: boolean
logo?: string
}>
// Transform to slim format (without logos and featured).
const registries = directory.map((entry) => ({
name: entry.name,
homepage: entry.homepage,
url: entry.url,
description: entry.description,
}))
// Write to public/r/registries.json.
const outputPath = path.join(process.cwd(), "public/r/registries.json")
await fs.writeFile(outputPath, JSON.stringify(registries, null, 2))
// Format with prettier.
await new Promise<void>((resolve, reject) => {
execFile("prettier", ["--write", outputPath], (error) => {
if (error) {
reject(error)
} else {
resolve()
}
})
})
}

View File

@@ -0,0 +1,247 @@
import path from "path"
import { getRegistries } from "@/src/registry/api"
import { BUILTIN_REGISTRIES } from "@/src/registry/constants"
import { handleError } from "@/src/utils/handle-error"
import { highlighter } from "@/src/utils/highlighter"
import { logger } from "@/src/utils/logger"
import { spinner } from "@/src/utils/spinner"
import { Command } from "commander"
import fs from "fs-extra"
import prompts from "prompts"
import { z } from "zod"
const addOptionsSchema = z.object({
cwd: z.string(),
silent: z.boolean(),
})
export const add = new Command()
.name("add")
.description("add registries to your project")
.argument(
"[registries...]",
"registries (@namespace) or registry URLs (@namespace=url)"
)
.option(
"-c, --cwd <cwd>",
"the working directory. defaults to the current directory.",
process.cwd()
)
.option("-s, --silent", "mute output.", false)
.action(async (registries: string[], opts) => {
try {
const options = addOptionsSchema.parse({
cwd: path.resolve(opts.cwd),
silent: opts.silent,
})
const registryArgs =
registries.length > 0
? registries
: await promptForRegistries({ silent: options.silent })
await addRegistriesToConfig(registryArgs, options.cwd, {
silent: options.silent,
})
} catch (error) {
logger.break()
handleError(error)
}
})
export function parseRegistryArg(arg: string): {
namespace: string
url?: string
} {
const [namespace, ...rest] = arg.split("=")
const url = rest.length > 0 ? rest.join("=") : undefined
if (!namespace.startsWith("@")) {
throw new Error(
`Invalid registry namespace: ${highlighter.info(namespace)}. ` +
`Registry names must start with @ (e.g., @acme).`
)
}
return { namespace, url }
}
function pluralize(count: number, singular: string, plural: string) {
return `${count} ${count === 1 ? singular : plural}`
}
async function addRegistriesToConfig(
registryArgs: string[],
cwd: string,
options: { silent?: boolean }
) {
const configPath = path.resolve(cwd, "components.json")
if (!fs.existsSync(configPath)) {
throw new Error(
`No ${highlighter.info("components.json")} found. Run ${highlighter.info(
"shadcn init"
)} first.`
)
}
const parsed = registryArgs.map(parseRegistryArg)
const needsLookup = parsed.filter((p) => !p.url)
let registriesIndex: { name: string; url: string }[] = []
if (needsLookup.length > 0) {
const fetchSpinner = spinner("Fetching registries.", {
silent: options.silent,
}).start()
const registries = await getRegistries()
if (!registries) {
fetchSpinner.fail()
throw new Error("Failed to fetch registries.")
}
fetchSpinner.succeed()
registriesIndex = registries
}
const registriesToAdd: Record<string, string> = {}
for (const { namespace, url } of parsed) {
if (namespace in BUILTIN_REGISTRIES) {
logger.warn(
`${highlighter.info(
namespace
)} is a built-in registry and cannot be added.`
)
continue
}
if (url) {
if (!url.includes("{name}")) {
throw new Error(
`Invalid registry URL for ${highlighter.info(
namespace
)}. URL must include {name} placeholder. Example: ${highlighter.info(
`${namespace}=https://example.com/r/{name}.json`
)}`
)
}
registriesToAdd[namespace] = url
} else {
const registry = registriesIndex.find((r) => r.name === namespace)
if (!registry) {
throw new Error(
`Registry ${highlighter.info(namespace)} not found. ` +
`Provide a URL: ${highlighter.info(
`${namespace}=https://.../{name}.json`
)}`
)
}
registriesToAdd[namespace] = registry.url
}
}
if (Object.keys(registriesToAdd).length === 0) {
return { addedRegistries: [] }
}
const existingConfig = await fs.readJson(configPath)
const existingRegistries = existingConfig.registries || {}
const newRegistries: Record<string, string> = {}
const skipped: string[] = []
for (const [ns, url] of Object.entries(registriesToAdd)) {
if (existingRegistries[ns]) {
skipped.push(ns)
} else {
newRegistries[ns] = url
}
}
if (Object.keys(newRegistries).length === 0) {
if (skipped.length > 0 && !options.silent) {
spinner(
`Skipped ${pluralize(
skipped.length,
"registry",
"registries"
)}: (already configured)`,
{ silent: options.silent }
)?.info()
for (const name of skipped) {
logger.log(` - ${name}`)
}
} else if (!options.silent) {
logger.info("No new registries to add.")
}
return
}
const updatedConfig = {
...existingConfig,
registries: {
...existingRegistries,
...newRegistries,
},
}
const writeSpinner = spinner("Updating components.json.", {
silent: options.silent,
}).start()
await fs.writeJson(configPath, updatedConfig, { spaces: 2 })
writeSpinner.succeed()
if (!options.silent) {
const newRegistryNames = Object.keys(newRegistries)
spinner(
`Added ${pluralize(newRegistryNames.length, "registry", "registries")}:`,
{ silent: options.silent }
)?.succeed()
for (const name of newRegistryNames) {
logger.log(` - ${name}`)
}
if (skipped.length > 0) {
spinner(
`Skipped ${pluralize(
skipped.length,
"registry",
"registries"
)}: (already configured)`,
{ silent: options.silent }
)?.info()
for (const name of skipped) {
logger.log(` - ${name}`)
}
}
}
}
async function promptForRegistries(options: { silent?: boolean }) {
const fetchSpinner = spinner("Fetching registries.", {
silent: options.silent,
}).start()
const registries = await getRegistries()
if (!registries) {
fetchSpinner.fail()
throw new Error("Failed to fetch registries.")
}
fetchSpinner.succeed()
const sorted = [...registries].sort((a, b) => a.name.localeCompare(b.name))
const { selected } = await prompts({
type: "autocompleteMultiselect",
name: "selected",
message: "Which registries would you like to add?",
hint: "Space to select. A to toggle all. Enter to submit.",
instructions: false,
choices: sorted.map((r) => ({
title: r.name,
description: r.description,
value: r.name,
})),
})
if (!selected?.length) {
logger.warn("No registries selected. Exiting.")
logger.info("")
process.exit(1)
}
return selected as string[]
}

View File

@@ -0,0 +1,7 @@
import { add } from "@/src/commands/registry/add"
import { Command } from "commander"
export const registry = new Command()
.name("registry")
.description("manage registries")
.addCommand(add)

View File

@@ -7,6 +7,7 @@ import { info } from "@/src/commands/info"
import { init } from "@/src/commands/init"
import { mcp } from "@/src/commands/mcp"
import { migrate } from "@/src/commands/migrate"
import { registry } from "@/src/commands/registry"
import { build as registryBuild } from "@/src/commands/registry/build"
import { mcp as registryMcp } from "@/src/commands/registry/mcp"
import { search } from "@/src/commands/search"
@@ -39,7 +40,8 @@ async function main() {
.addCommand(info)
.addCommand(build)
.addCommand(mcp)
// Registry commands
.addCommand(registry)
// Legacy registry commands.
program.addCommand(registryBuild).addCommand(registryMcp)
program.parse()

View File

@@ -28,6 +28,7 @@ import {
import { z } from "zod"
import {
getRegistries,
getRegistriesConfig,
getRegistriesIndex,
getRegistry,
@@ -104,11 +105,24 @@ const server = setupServer(
})
}),
http.get(`${REGISTRY_URL}/registries.json`, () => {
return HttpResponse.json({
"@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json",
"@example": "https://example.com/registry/styles/{style}/{name}.json",
"@test": "https://test.com/registry/{name}.json",
})
return HttpResponse.json([
{
name: "@shadcn",
url: "https://ui.shadcn.com/r/styles/{style}/{name}.json",
description: "The official shadcn/ui registry.",
},
{
name: "@example",
homepage: "https://example.com",
url: "https://example.com/registry/styles/{style}/{name}.json",
description: "An example registry for testing.",
},
{
name: "@test",
url: "https://test.com/registry/{name}.json",
description: "A test registry.",
},
])
})
)
@@ -1664,10 +1678,70 @@ describe("getRegistriesConfig", () => {
})
})
describe("getRegistries", () => {
it("should fetch and parse registries successfully", async () => {
const result = await getRegistries()
expect(result).toEqual([
{
name: "@shadcn",
url: "https://ui.shadcn.com/r/styles/{style}/{name}.json",
description: "The official shadcn/ui registry.",
},
{
name: "@example",
homepage: "https://example.com",
url: "https://example.com/registry/styles/{style}/{name}.json",
description: "An example registry for testing.",
},
{
name: "@test",
url: "https://test.com/registry/{name}.json",
description: "A test registry.",
},
])
})
it("should respect cache options", async () => {
const result1 = await getRegistries({ useCache: false })
expect(result1).toBeDefined()
const result2 = await getRegistries({ useCache: true })
expect(result2).toBeDefined()
expect(result1).toEqual(result2)
})
it("should handle network errors properly", async () => {
server.use(
http.get(`${REGISTRY_URL}/registries.json`, () => {
return new HttpResponse(null, { status: 500 })
})
)
await expect(getRegistries({ useCache: false })).rejects.toThrow()
})
it("should handle invalid JSON response", async () => {
server.use(
http.get(`${REGISTRY_URL}/registries.json`, () => {
return HttpResponse.json({
invalid: "format",
})
})
)
await expect(getRegistries({ useCache: false })).rejects.toThrow(
RegistriesIndexParseError
)
})
})
describe("getRegistriesIndex", () => {
it("should fetch and parse the registries index successfully", async () => {
const result = await getRegistriesIndex()
// getRegistriesIndex transforms array format to object format.
expect(result).toEqual({
"@shadcn": "https://ui.shadcn.com/r/styles/{style}/{name}.json",
"@example": "https://example.com/registry/styles/{style}/{name}.json",

View File

@@ -27,6 +27,7 @@ import {
configJsonSchema,
iconsSchema,
registriesIndexSchema,
registriesSchema,
registryBaseColorSchema,
registryConfigSchema,
registryIndexSchema,
@@ -279,7 +280,8 @@ export async function getItemTargetPath(
)
}
export async function getRegistriesIndex(options?: { useCache?: boolean }) {
// Fetch registries with new schema (array of objects with name, homepage, url, featured).
export async function getRegistries(options?: { useCache?: boolean }) {
options = {
useCache: true,
...options,
@@ -291,7 +293,7 @@ export async function getRegistriesIndex(options?: { useCache?: boolean }) {
})
try {
return registriesIndexSchema.parse(data)
return registriesSchema.parse(data)
} catch (error) {
if (error instanceof z.ZodError) {
throw new RegistriesIndexParseError(error)
@@ -301,6 +303,18 @@ export async function getRegistriesIndex(options?: { useCache?: boolean }) {
}
}
/**
* @deprecated Use getRegistries() instead.
*/
export async function getRegistriesIndex(options?: { useCache?: boolean }) {
// Fetch new format and transform to old Record<string, string> for backward compatibility.
const registries = await getRegistries(options)
if (!registries) return null
return Object.fromEntries(registries.map((r) => [r.name, r.url])) as z.infer<
typeof registriesIndexSchema
>
}
export async function getPresets(options?: { useCache?: boolean }) {
options = {
useCache: true,

View File

@@ -1,4 +1,5 @@
export {
getRegistries,
getRegistryItems,
resolveRegistryItems,
getRegistry,

View File

@@ -260,11 +260,22 @@ export const searchResultsSchema = z.object({
items: z.array(searchResultItemSchema),
})
// Legacy schema for getRegistriesIndex() backward compatibility.
export const registriesIndexSchema = z.record(
z.string().regex(/^@[a-zA-Z0-9][a-zA-Z0-9-_]*$/),
z.string()
)
// New schema for getRegistries().
export const registriesSchema = z.array(
z.object({
name: z.string(),
homepage: z.string().optional(),
url: z.string(),
description: z.string().optional(),
})
)
export const presetSchema = z.object({
name: z.string(),
title: z.string(),

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from "vitest"
import { parseRegistryArg } from "../../src/commands/registry/add"
describe("parseRegistryArg", () => {
it("should parse namespace without URL", () => {
expect(parseRegistryArg("@magicui")).toEqual({ namespace: "@magicui" })
expect(parseRegistryArg("@aceternity")).toEqual({ namespace: "@aceternity" })
})
it("should parse namespace with URL", () => {
expect(
parseRegistryArg("@mycompany=https://example.com/r/{name}.json")
).toEqual({
namespace: "@mycompany",
url: "https://example.com/r/{name}.json",
})
})
it("should handle URL with query params containing =", () => {
expect(
parseRegistryArg("@foo=https://example.com/r/{name}.json?token=abc")
).toEqual({
namespace: "@foo",
url: "https://example.com/r/{name}.json?token=abc",
})
})
it("should handle URL with multiple = in query params", () => {
expect(
parseRegistryArg(
"@bar=https://example.com/r/{name}.json?token=abc&key=xyz"
)
).toEqual({
namespace: "@bar",
url: "https://example.com/r/{name}.json?token=abc&key=xyz",
})
})
it("should handle URL with port number", () => {
expect(
parseRegistryArg("@local=http://localhost:8080/r/{name}.json")
).toEqual({
namespace: "@local",
url: "http://localhost:8080/r/{name}.json",
})
})
it("should throw for namespace without @", () => {
expect(() => parseRegistryArg("foo")).toThrow("must start with @")
expect(() => parseRegistryArg("magicui")).toThrow("must start with @")
expect(() =>
parseRegistryArg("mycompany=https://example.com/r/{name}.json")
).toThrow("must start with @")
})
})

View File

@@ -375,3 +375,96 @@ describe("shadcn add", () => {
).toBe(true)
})
})
describe("shadcn registry add", () => {
it("should add registry from index to components.json", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
await npxShadcn(fixturePath, ["registry", "add", "@magicui"])
const componentsJson = await fs.readJson(
path.join(fixturePath, "components.json")
)
expect(componentsJson.registries).toBeDefined()
expect(componentsJson.registries["@magicui"]).toBeDefined()
expect(componentsJson.registries["@magicui"]).toContain("{name}")
})
it("should add custom registry with URL", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
await npxShadcn(fixturePath, [
"registry",
"add",
"@mycompany=https://example.com/r/{name}.json",
])
const componentsJson = await fs.readJson(
path.join(fixturePath, "components.json")
)
expect(componentsJson.registries["@mycompany"]).toBe(
"https://example.com/r/{name}.json"
)
})
it("should add multiple registries", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
await npxShadcn(fixturePath, ["registry", "add", "@magicui", "@aceternity"])
const componentsJson = await fs.readJson(
path.join(fixturePath, "components.json")
)
expect(componentsJson.registries["@magicui"]).toBeDefined()
expect(componentsJson.registries["@aceternity"]).toBeDefined()
})
it("should skip already configured registries", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
await npxShadcn(fixturePath, ["registry", "add", "@magicui"])
// Add again - should not error.
await npxShadcn(fixturePath, ["registry", "add", "@magicui"])
const componentsJson = await fs.readJson(
path.join(fixturePath, "components.json")
)
expect(componentsJson.registries["@magicui"]).toBeDefined()
})
it("should error for registry not in index without URL", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
const result = await npxShadcn(fixturePath, [
"registry",
"add",
"@nonexistent-registry-12345",
])
expect(result.exitCode).toBe(1)
expect(result.stdout).toContain("not found")
})
it("should error for invalid URL missing {name}", async () => {
const fixturePath = await createFixtureTestDirectory("next-app")
await npxShadcn(fixturePath, ["init", "--base-color=neutral"])
const result = await npxShadcn(fixturePath, [
"registry",
"add",
"@foo=https://example.com/bad.json",
])
expect(result.exitCode).toBe(1)
expect(result.stdout).toContain("{name}")
})
it("should require components.json for adding registries", async () => {
const fixturePath = await createFixtureTestDirectory("no-framework")
// No init, so no components.json.
const result = await npxShadcn(fixturePath, ["registry", "add", "@magicui"])
expect(result.exitCode).toBe(1)
expect(result.stdout).toContain("components.json")
})
})

View File

@@ -1188,7 +1188,7 @@ describe("registries", () => {
describe("registries:init", () => {
beforeEach(async () => {
process.env.REGISTRY_URL = "http://localhost:4000/r"
process.env.REGISTRY_URL = "http://localhost:4040/r"
})
it("should error when init with unconfigured registries", async () => {

View File

@@ -15,11 +15,11 @@ export async function createRegistryServer(
const server = createServer((request, response) => {
const urlRaw = request.url?.split("?")[0]
// Handle registries.json endpoint (don't strip .json for this one)
// Handle registries.json endpoint (don't strip .json for this one).
if (urlRaw?.endsWith("/registries.json")) {
response.writeHead(200, { "Content-Type": "application/json" })
// Return empty registry index for tests - we want to test manual configuration.
response.end(JSON.stringify({}))
// Return empty registry array for tests - we want to test manual configuration.
response.end(JSON.stringify([]))
return
}
@@ -103,7 +103,23 @@ export async function createRegistryServer(
return
}
if (urlWithoutQuery?.includes("index")) {
// Match /styles/{style}/index for the registry style index (e.g., /styles/new-york-v4/index).
if (urlWithoutQuery?.match(/\/styles\/[^/]+\/index$/)) {
response.writeHead(200, { "Content-Type": "application/json" })
response.end(
JSON.stringify({
name: "index",
type: "registry:style",
registryDependencies: ["utils"],
cssVars: {},
files: [],
})
)
return
}
// Match /r/index for the registry index (but NOT paths like /styles/foo/index).
if (urlWithoutQuery?.match(/\/r\/index$/)) {
response.writeHead(200, { "Content-Type": "application/json" })
response.end(
JSON.stringify([

View File

@@ -1 +0,0 @@
apps/v4/public/r/registries.json