- Add product vision/market research doc (VISION.md) - Set up shadcn/ui (radix + nova preset) with blue/orange theme, Outfit + Work Sans fonts - Landing page: hero with rank input, problem/steps sections, comparison table, FAQ - Data pipeline: CSV archive ingest (2021-2024) + live YÖK Atlas API refresh (2025) into SQLite (npm run ingest / refresh); zeros normalized to NULL - /sonuc page: hayal/dengeli/garanti buckets by COALESCE(sira2025, sira2024), score-type switcher, 2024→2025 trend indicators - Add ui-ux-pro-max and shadcn agent skills, shadcn MCP config, launch.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
174 lines
9.1 KiB
Markdown
174 lines
9.1 KiB
Markdown
---
|
|
name: migrate-radix-to-base
|
|
description: 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.
|
|
---
|
|
|
|
# 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 the state; resume,
|
|
never restart.
|
|
2. If the component imports other ui wrappers still on radix (select ->
|
|
button), STOP and recommend migrating those first, bottom-up.
|
|
3. Write the migrated version to `<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.
|
|
|
|
## Hard rules
|
|
|
|
- NEVER touch non-radix libraries or their wrappers: cmdk (command), vaul
|
|
(drawer), sonner, input-otp, react-day-picker (calendar), recharts (chart).
|
|
Report them as intentionally untouched.
|
|
- No Base UI counterpart: AspectRatio -> CSS aspect-ratio div; Label ->
|
|
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, never
|
|
a hand-rolled useRender wrapper.
|
|
- Behavior deltas are FLAGGED, never silently patched (tabs manual
|
|
activation, menu items not closing on click, nav-menu 50ms delay). The
|
|
target is idiomatic Base UI matching the shadcn base registry.
|
|
- Honest reporting: skipped/reverted files are listed as flagged, never as
|
|
migrated. Pre-existing failures are named as pre-existing.
|
|
|
|
## Verify and report
|
|
|
|
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:
|
|
- Each run writes (or fully overwrites) the file for each component it
|
|
migrated. Re-running a component replaces its report; never touch other
|
|
components' files.
|
|
- A multi-component run ("migrate alert-dialog and dropdown-menu") writes one
|
|
file per component, each self-contained; shared consumer-sweep notes are
|
|
repeated in every affected file.
|
|
- Whole-project mode writes the per-component files plus
|
|
`.migration/project.md` (dependency swap, app-code sweep summary, final
|
|
build result).
|
|
- There is NO index file. Migration status is derived from disk, not
|
|
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>
|
|
```
|