Migrate React projects and shadcn components from Radix UI to Base UI, handling route objects, consumer props, and progressive wrapper migration.
Real-world examples not captured yet
This skill shows a gallery of live HTML demos (CDN libraries welcome — GSAP, Three.js, etc.) rendered inline via srcdoc.
Skill markdown
# Radix UI -> Base UI migration
You migrate shadcn wrappers, hand-rolled radix compositions, and their
consumers to `@base-ui/react`, keeping the project buildable at every step.
Be precise; never guess a mapping. When a prop or part is not in these
reference files, check `node_modules/@base-ui/react/**/*.d.ts` before
transforming, and record gaps in the report.
## Preflight (always)
1. `npx shadcn@latest info --json` (or the project's runner): gives the
current base, STYLE (e.g. `radix-lyra`), tailwind version, aliases,
installed components, and package manager. Trust it over inference.
2. Detect the package manager (packageManager field / lockfile:
pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json) and use IT for
every install. Never leave a stale lockfile.
3. Require a clean git tree; work on a branch; one commit per component.
4. Baseline check BEFORE touching dependencies: run the project's
typecheck/build so pre-existing failures are never attributed to you.
5. Install `@base-ui/react` alongside radix. Radix packages are removed only
after the LAST component is migrated (both coexist fine).
## Strategy: golden pair first, transformation engine second
- **Golden pair via the CLI (preferred).** If the project is shadcn with a
known style (`radix-<style>`), the shadcn CLI itself is the golden-pair
executor:
1. Classify each ui wrapper FIRST: diff the user's file against its stock
origin, using the components.json style VERBATIM in the URL
(`https://ui.shadcn.com/r/styles/<style>/<component>.json`,
files[0].content). This works for prefixed styles (radix-nova) AND
legacy unprefixed ones (new-york, new-york-v4, default), which are all
still served.
2. WHOLE-PROJECT mode: flip `components.json` style `radix-<style>` ->
`base-<style>` now. PROGRESSIVE mode: do NOT flip yet (the project is
still mostly radix; the flip happens once, after the last component);
fetch base variants directly by URL instead
(`https://ui.shadcn.com/r/styles/base-<style>/<component>.json`).
3. PRISTINE wrappers, whole-project mode: `shadcn add <component>
--overwrite` delivers the base variant with the project's exact
icon/font/preset resolution. Never bulk `--all --overwrite`; go
component by component, or you drown in unrelated registry version
drift. PROGRESSIVE mode: never use `--overwrite` (it destroys the
original that consumers still import); write the fetched base variant
content to `<component>-base.tsx` instead.
4. CUSTOMIZED wrappers: fetch the base variant and replay the user's diff
onto it (their customizations must SURVIVE; `--overwrite` would destroy
them). Mechanical implementation that works at scale:
`git merge-file user.tsx radix-golden.tsx base-golden.tsx` (three-way
merge, radix golden as ancestor) auto-resolves most files; hand-resolve
conflicts with the reference tables.
5. MANDATORY leftover sweep on EVERY golden-pair file, including ones that
merged "clean": `grep -n "radix-ui\|@radix-ui\|IconPlaceholder"` per
file. The registry sometimes reorders functions between variants, which
makes three-way merges report zero conflicts while leaving stale radix
hunks in place. A clean merge is NOT proof of a clean file.
This is more reliable than reconstructing transforms; use it whenever the
pair exists. Consumer/app code has no CLI mechanism: always hand-migrate it
against `consumer-props.md`.
- **Legacy styles (new-york, new-york-v4, default): classification only, no
replay.** These have no base counterpart (there is no base-new-york), and
retargeting onto a base-<style> variant would restyle the user's app. Use
the radix golden ONLY to detect customizations, then run the transformation
engine on the user's OWN file: rewire primitives, keep their exact classes,
apply class-mapping renames. Their look stays theirs. At the end of a
legacy whole-project migration, FLAG (do not fix): the style name still
reads as radix to the CLI, so future `shadcn add` will deliver radix
variants; the user decides whether to switch style or add manually.
- **Transformation engine (fallback).** Hand-rolled radix code, non-shadcn
projects, unknown styles: transform using `universal-patterns.md` (imports
in BOTH forms: `radix-ui` and `@radix-ui/react-*`; asChild->render with the
worked example; Portal>Positioner>Popup; the positioner FORWARD rule; part
renames), the per-family props tables (`overlays.md`, `menus.md`,
`form-controls.md`, `disclosure.md`, `display-misc.md`), `class-mapping.md`
for data-attribute/CSS-var rewrites, and `wrapper-shapes.md` for exact
target shapes (tooltip arrow, SubContent defaults, select anatomy).
## Modes
**Progressive (default).** "Migrate accordion" = one component, strangler-fig:
1. Detect in-progress state first: an existing `<component>-base.tsx`,
consumers split between old/new imports. The files ARE th
…