Rob Austin - Sep 8, 2026
How the Shadcnblocks registry decides between Radix UI, Base UI and React Aria
Every block on shadcnblocks.com is available in 3 versions for each major “primitive” UI library: Radix UI, Base UI or React Aria.
Each block is installed using the same universal Shadcn CLI command ie npx shadcn add @shadcnblocks/hero1 and automatically resolves the correct version into your project. But how does that magic work?
The choice is made by the “style” field in your project’s components.json.
{ "style": "base-vega", "registries": { "@shadcnblocks": "https://www.shadcnblocks.com/r/{style}/{name}" }}
The style field is the value that defines which primitive library and which style is installed. Examples are radix-nova, base-vega, aria-lyra, and so on. shadcn uses it to decide which version of button, dialog, etc. to install into components/ui. We piggyback on the same value.
{style} in the registry URL is a placeholder the CLI fills in from that setting, alongside {name}.
What the CLI actually requests
Run npx shadcn add @shadcnblocks/hero1 in a base-vega project and the CLI substitutes both placeholders and fetches:
https://www.shadcnblocks.com/r/base-vega/hero1
In a radix-nova project the same command fetches
https://www.shadcnblocks.com/r/radix-nova/hero1
Same block id, different URL, and the first path segment carries the information about your projects primitives and style.
You can skip the CLI and fetch those URLs directly — the registry is plain JSON. Free blocks need no auth:
curl -s https://www.shadcnblocks.com/r/radix-nova/hero1 | jq -r '.files[0].content' | grep -n 'asChild\|render='curl -s https://www.shadcnblocks.com/r/base-vega/hero1 | jq -r '.files[0].content' | grep -n 'asChild\|render='
The first prints lines with asChild (Radix composition), the second prints lines with render= and nativeButton={false} (Base composition).
What our registry server does with the URL
The route behind /r/… reads the first segment and maps it to a primitive library:
| First segment | Library served | Notes |
|---|---|---|
radix-vega, radix-nova, radix-maia, radix-lyra, radix-mira, radix-luma, radix-sera, radix-rhea | Radix UI | |
base-vega, base-nova, base-maia, base-lyra, base-mira, base-luma, base-sera, base-rhea | Base UI | |
new-york-v4, new-york, default | Radix UI | Older shadcn styles; pre-date Base UI |
aria-vega, aria-nova, aria-maia, aria-lyra, aria-mira, aria-luma, aria-sera, aria-rhea | React Aria | Same rules as Base UI |
(none — the old /r/{name} URL) | Radix UI | No way to know your style, so Radix |
Then it loads the pre-generated JSON for that library: /r/radix/hero1.json, /r/base/hero1.json or /r/aria/hero1.json. Those files are built ahead of time, not transformed on the fly — what you fetch is exactly what’s committed and tested.
Things that look like they should matter but don’t
The block id. There is no hero1-base. Don’t go looking.
Putting a library in the id. npx shadcn add @shadcnblocks/base/hero1 in a radix-nova project requests /r/radix-nova/base/hero1. We strip the base/ segment so the item still resolves, but the style wins: you get Radix,
The Radix / Base / React Aria toggle on shadcnblocks.com. The switch in the Customize panel changes what you preview and what the Code tab shows (there’s a Diff view that lines the builds up against Radix). It does not change the CLI command on the page — that’s always @shadcnblocks/{id}, because your components.json is the only thing that should decide.
Third-party components: Kibo UI, Magic UI, DiceUI
Some blocks use components from other registries. This is where the builds diverge in a less obvious way.
Radix style → the upstream registry. The Radix item for a block that uses Kibo’s dialog stack depends on Kibo directly:
"registryDependencies": ["https://www.kibo-ui.com/r/dialog-stack.json"]
The CLI fetches it from kibo-ui.com and writes components/kibo-ui/dialog-stack/index.tsx. We don’t touch Kibo’s code for Radix users.
Base style → our copy, when we have to. Kibo only publishes a Radix build, and it composes with asChild (Radix Slot). When the CLI installs a file into a base-* project it rewrites every asChild to render, and Kibo’s Slot-based file breaks. So the Base item for the same block depends on a Base-compatible copy we host:
"registryDependencies": ["https://www.shadcnblocks.com/r/base/kibo-ui/dialog-stack"]
It installs to the same path, so the block’s import … from "@/components/kibo-ui/dialog-stack" is identical either way. These copies are free and unauthenticated:
curl -s https://www.shadcnblocks.com/r/base/kibo-ui/dialog-stack | jq -r '.files[0].content' | grep -n 'useRender\|render'
Only components that actually break under Base get a copy — ten Kibo components (banner, choicebox, code-block, color-picker, dialog-stack, editor, gantt, image-crop, snippet, tags) and Magic UI’s rainbow-button. Kibo components that never receive asChild from a block (ticker, tree, mini-calendar, …) keep pointing at kibo-ui.com for Base users too.
React Aria style → the same idea, a different list. React Aria items depend on https://www.shadcnblocks.com/r/aria/kibo-ui/{name} for the fourteen Kibo components that need a React Aria rewrite (banner, choicebox, code-block, color-picker, dropzone, editor, gantt, glimpse, image-crop, kanban, mini-calendar, reel, snippet, tags). Everything else, including Magic UI’s rainbow-button, keeps pointing upstream.
DiceUI publishes its own Base build, so we do nothing special: both items reference @diceui/stepper, and your {style} selects DiceUI’s Radix or Base version upstream.
If you only remember one thing
Your components.json style decides. Put {style} in the registry URL, set style to radix-*, base-* or aria-*, and install blocks by their plain id. Everything else — the Radix/Base/React Aria toggle on the site, prefixes in the id, the block’s name — is either a preview convenience or ignored.