$npx -y skills add shadcn-ui/ui --skill migrate-radix-to-baseMigrates React projects and components from Radix UI to Base UI. Use when asked to migrate from radix, move to base-ui, convert radix primitives, or switch a shadcn project's base library. Handles single components ("migrate accordion") and whole projects.
| 1 | # Radix UI -> Base UI migration |
| 2 | |
| 3 | You migrate shadcn wrappers, hand-rolled radix compositions, and their |
| 4 | consumers to `@base-ui/react`, keeping the project buildable at every step. |
| 5 | Be precise; never guess a mapping. When a prop or part is not in these |
| 6 | reference files, check `node_modules/@base-ui/react/**/*.d.ts` before |
| 7 | transforming, and record gaps in the report. |
| 8 | |
| 9 | ## Preflight (always) |
| 10 | |
| 11 | 1. `npx shadcn@latest info --json` (or the project's runner): gives the |
| 12 | current base, STYLE (e.g. `radix-lyra`), tailwind version, aliases, |
| 13 | installed components, and package manager. Trust it over inference. |
| 14 | 2. Detect the package manager (packageManager field / lockfile: |
| 15 | pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json) and use IT for |
| 16 | every install. Never leave a stale lockfile. |
| 17 | 3. Require a clean git tree; work on a branch; one commit per component. |
| 18 | 4. Baseline check BEFORE touching dependencies: run the project's |
| 19 | typecheck/build so pre-existing failures are never attributed to you. |
| 20 | 5. Install `@base-ui/react` alongside radix. Radix packages are removed only |
| 21 | after the LAST component is migrated (both coexist fine). |
| 22 | |
| 23 | ## Strategy: golden pair first, transformation engine second |
| 24 | |
| 25 | - **Golden pair via the CLI (preferred).** If the project is shadcn with a |
| 26 | known style (`radix-<style>`), the shadcn CLI itself is the golden-pair |
| 27 | executor: |
| 28 | 1. Classify each ui wrapper FIRST: diff the user's file against its stock |
| 29 | origin, using the components.json style VERBATIM in the URL |
| 30 | (`https://ui.shadcn.com/r/styles/<style>/<component>.json`, |
| 31 | files[0].content). This works for prefixed styles (radix-nova) AND |
| 32 | legacy unprefixed ones (new-york, new-york-v4, default), which are all |
| 33 | still served. |
| 34 | 2. WHOLE-PROJECT mode: flip `components.json` style `radix-<style>` -> |
| 35 | `base-<style>` now. PROGRESSIVE mode: do NOT flip yet (the project is |
| 36 | still mostly radix; the flip happens once, after the last component); |
| 37 | fetch base variants directly by URL instead |
| 38 | (`https://ui.shadcn.com/r/styles/base-<style>/<component>.json`). |
| 39 | 3. PRISTINE wrappers, whole-project mode: `shadcn add <component> |
| 40 | --overwrite` delivers the base variant with the project's exact |
| 41 | icon/font/preset resolution. Never bulk `--all --overwrite`; go |
| 42 | component by component, or you drown in unrelated registry version |
| 43 | drift. PROGRESSIVE mode: never use `--overwrite` (it destroys the |
| 44 | original that consumers still import); write the fetched base variant |
| 45 | content to `<component>-base.tsx` instead. |
| 46 | 4. CUSTOMIZED wrappers: fetch the base variant and replay the user's diff |
| 47 | onto it (their customizations must SURVIVE; `--overwrite` would destroy |
| 48 | them). Mechanical implementation that works at scale: |
| 49 | `git merge-file user.tsx radix-golden.tsx base-golden.tsx` (three-way |
| 50 | merge, radix golden as ancestor) auto-resolves most files; hand-resolve |
| 51 | conflicts with the reference tables. |
| 52 | 5. MANDATORY leftover sweep on EVERY golden-pair file, including ones that |
| 53 | merged "clean": `grep -n "radix-ui\|@radix-ui\|IconPlaceholder"` per |
| 54 | file. The registry sometimes reorders functions between variants, which |
| 55 | makes three-way merges report zero conflicts while leaving stale radix |
| 56 | hunks in place. A clean merge is NOT proof of a clean file. |
| 57 | This is more reliable than reconstructing transforms; use it whenever the |
| 58 | pair exists. Consumer/app code has no CLI mechanism: always hand-migrate it |
| 59 | against `consumer-props.md`. |
| 60 | - **Legacy styles (new-york, new-york-v4, default): classification only, no |
| 61 | replay.** These have no base counterpart (there is no base-new-york), and |
| 62 | retargeting onto a base-<style> variant would restyle the user's app. Use |
| 63 | the radix golden ONLY to detect customizations, then run the transformation |
| 64 | engine on the user's OWN file: rewire primitives, keep their exact classes, |
| 65 | apply class-mapping renames. Their look stays theirs. At the end of a |
| 66 | legacy whole-project migration, FLAG (do not fix): the style name still |
| 67 | reads as radix to the CLI, so future `shadcn add` will deliver radix |
| 68 | variants; the user decides whether to switch style or add manually. |
| 69 | - **Transformation engine (fallback).** Hand-rolled radix code, non-shadcn |
| 70 | projects, unknown styles: transform using `universal-patterns.md` (imports |
| 71 | in BOTH forms: `radix-ui` and `@radix-ui/react-*`; asChild->render with the |
| 72 | worked example; Portal>Positioner>Popup; the positioner FORWARD rule; part |
| 73 | renames), the per-family props tables (`overlays.md`, `menus.md`, |
| 74 | `form-controls.md`, `disclosure.md`, `display-misc.md`), `class-mapping.md` |
| 75 | for data-attribute/CSS-var rewrites, and `wrapper-shape |