Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrates 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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 39% | 0% |
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.
npx shadcn@latest info --json (or the project's runner): gives thecurrent base, STYLE (e.g. radix-lyra), tailwind version, aliases, installed components, and package manager. Trust it over inference.
pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json) and use IT for every install. Never leave a stale lockfile.
typecheck/build so pre-existing failures are never attributed to you.
@base-ui/react alongside radix. Radix packages are removed onlyafter the LAST component is migrated (both coexist fine).
known style (radix-<style>), the shadcn CLI itself is the golden-pair executor:
origin, using the components.json style VERBATIM in the URL (https://ui.shadcn.com/r/styles/<style>/<component>.json, files0].content). This works for prefixed styles (radix-nova) AND legacy unprefixed ones (new-york, new-york-v4, default), which are all still served.
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).
--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.
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.
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.
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.
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).
Progressive (default). "Migrate accordion" = one component, strangler-fig:
<component>-base.tsx,consumers split between old/new imports. The files ARE the state; resume, never restart.
button), STOP and recommend migrating those first, bottom-up.
<component>-base.tsx (original untouched;golden-pair content fetched by URL, or transformed by hand, per the strategy above); typecheck. Repoint consumers ONE AT A TIME (imports + the call-site props in consumer-props.md); typecheck each. When no consumer imports the original: delete it, rename -base -> original, flip imports back, final check, commit. When the LAST radix wrapper in the project is finalized, flip components.json to base-<style> and remove radix deps.
Whole project (only when explicitly asked): same per-component work in dependency order (leaf/shared wrappers like button and label first). After wrappers, sweep ALL app code against consumer-props.md — the call-site break surface is much larger than asChild. Then remove radix deps, install, full build.
(drawer), sonner, input-otp, react-day-picker (calendar), recharts (chart). Report them as intentionally untouched.
native <label>; VisuallyHidden -> sr-only; Direction -> Direction Provider (direction prop, not dir). Popover Anchor and NavigationMenu Indicator have no equivalent: inert passthrough + flag.
button.tsx migrates to the REAL @base-ui/react/button primitive, nevera hand-rolled useRender wrapper.
activation, menu items not closing on click, nav-menu 50ms delay). The target is idiomatic Base UI matching the shadcn base registry.
migrated. Pre-existing failures are named as pre-existing.
Typecheck per file, build per batch, full build at the end vs the baseline.
Reports live in a .migration/ directory at the project root, ONE FILE PER COMPONENT: .migration/<component>.md (e.g. .migration/accordion.md). Rules:
migrated. Re-running a component replaces its report; never touch other components' files.
file per component, each self-contained; shared consumer-sweep notes are repeated in every affected file.
.migration/project.md (dependency swap, app-code sweep summary, final build result).
maintained: scan the project's ui directory (the ui alias from shadcn info, e.g. components/ui or src/components/ui) for remaining radix imports when asked "what's left". End every run's summary with that derived count ("N wrappers remain on Radix").
Each .migration/<component>.md uses EXACTLY this structure (it is documented publicly; reports must match it):
md# <component> <date, strategy used (golden pair via CLI / merge / engine), one-line verdict> ## Changed <every file touched, with what changed and why; include file:line for anything notable. Confirm the leftover scan is clean: grep -n "radix-ui\|@radix-ui" on this component's files> ## Left alone <files that look related but were intentionally not touched, with the reason (cmdk/vaul/sonner are not radix; unrelated drift; etc.)> ## Behavior changes <differences that compile fine but act differently; flagged, never patched (tabs activation, menu close-on-click, delays...). Empty section if none> ## Verify by hand <short manual QA checklist for this primitive family: focus return on dialogs, keyboard nav + typeahead on menus/select, tooltip delay feel, slider commit events. Concrete steps, one minute of clicking>
Other measured skills in the registry, with their headline benchmark lift.