Migración de Radix UI a Base UI
Migras wrappers de shadcn, composiciones Radix hechas a mano y sus consumidores a @base-ui/react, manteniendo el proyecto compilable en cada paso. Sé preciso; nunca adivines un mapeo. Cuando una prop o parte no esté en estos archivos de referencia, revisa node_modules/@base-ui/react/**/*.d.ts antes de transformar, y registra las brechas en el informe.
Preflight (siempre)
npx shadcn@latest info --json(o el runner del proyecto): da la base actual, STYLE (ej.radix-lyra), versión de tailwind, alias, componentes instalados y gestor de paquetes. Confía en ello sobre la inferencia.- Detecta el gestor de paquetes (campo packageManager / lockfile: pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json) y úsalo para CADA instalación. Nunca dejes un lockfile obsoleto.
- Exige un árbol git limpio; trabaja en una rama; un commit por componente.
- Verificación base ANTES de tocar dependencias: ejecuta el typecheck/build del proyecto para que las fallas preexistentes nunca te sean atribuidas.
- Instala
@base-ui/reactjunto a radix. Los paquetes Radix se eliminan solo después de migrar el ÚLTIMO componente (ambos coexisten bien).
Estrategia: par dorado primero, motor de transformación segundo
- Par dorado vía CLI (preferido). Si el proyecto es shadcn con un estilo conocido (
radix-<style>), el propio CLI de shadcn es el ejecutor del par dorado:- Clasifica CADA wrapper de ui PRIMERO: compara el archivo del usuario con su origen estándar, usando el estilo de components.json TEXTUAL en la URL (
https://ui.shadcn.com/r/styles/<style>/<component>.json, files[0].content). Esto funciona para estilos prefijados (radix-nova) Y los heredados sin prefijo (new-york, new-york-v4, default), que aún se sirven. - Modo PROYECTO COMPLETO: cambia ahora el estilo de
components.jsonderadix-<style>abase-<style>. Modo PROGRESIVO: NO lo cambies aún (el proyecto sigue siendo mayoritariamente radix; el cambio ocurre una vez, después del último componente); obtén las variantes base directamente por URL (https://ui.shadcn.com/r/styles/base-<style>/<component>.json). - Wrappers SIN personalizar, modo proyecto completo:
shadcn add <component> --overwriteentrega la variante base con la resolución exacta de iconos/fuentes/ajustes preestablecidos del proyecto. Nunca hagas--all --overwrite; ve componente por componente, o te ahogarás en desviaciones de versión del registro no relacionadas. Modo PROGRESIVO: nunca uses--overwrite(destruye el original que los consumidores aún importan); escribe el contenido de la variante base obtenida en<component>-base.tsx. - Wrappers PERSONALIZADOS: obtén la variante base y reproduce el diff del usuario sobre ella (sus personalizaciones deben SOBREVIVIR;
--overwritelas destruiría). Implementación mecánica que funciona a escala:git merge-file user.tsx radix-golden.tsx base-golden.tsx(fusión a tres bandas, radix golden como ancestro) resuelve automáticamente la mayoría de archivos; resuelve conflictos manualmente con las tablas de referencia. - BARRIDO OBLIGATORIO de restos en CADA archivo del par dorado, incluidos los que se fusionaron "limpios":
grep -n "radix-ui\|@radix-ui\|IconPlaceholder"por archivo. El registro a veces reordena funciones entre variantes, lo que hace que las fusiones a tres bandas reporten cero conflictos mientras dejan fragmentos radix obsoletos. Una fusión limpia NO es prueba de un archivo limpio. Esto es más confiable que reconstruir transformaciones; úsalo siempre que el par exista. El código de aplicación/consumidor no tiene mecanismo CLI: migra siempre a mano contraconsumer-props.md.
- Clasifica CADA wrapper de ui PRIMERO: compara el archivo del usuario con su origen estándar, usando el estilo de components.json TEXTUAL en la URL (
- Estilos heredados (new-york, new-york-v4, default): solo clasificación, sin reproducción. Estos no tienen contraparte base (no existe base-new-york), y reorientarlos a una variante base- cambiaría el estilo de la aplicación del usuario. Usa el radix golden SOLO para detectar personalizaciones, luego ejecuta el motor de transformación sobre el archivo PROPIO del usuario: reconfigura primitivas, mantén sus clases exactas, aplica renombres de mapeo de clases. Su aspecto sigue siendo suyo. Al final de una migración de proyecto completo heredada, MARCA (no corrijas): el nombre del estilo aún se lee como radix para el CLI, por lo que futuros
shadcn addentregarán variantes radix; el usuario decide si cambiar el estilo o agregar manualmente. - Motor de transformación (fallback). Código Radix hecho a mano, proyectos no shadcn, estilos desconocidos: transforma usando
universal-patterns.md(imports en AMBAS formas:radix-uiy@radix-ui/react-*; asChild->render con el ejemplo trabajado; Portal>Positioner>Popup; la regla FORWARD del positioner; renombres de partes), las tablas de props por familia (overlays.md,menus.md,form-controls.md,disclosure.md,display-misc.md),class-mapping.mdpara reescrituras de data-attribute/CSS-var, ywrapper-shapes.mdpara las formas objetivo exactas (flecha de tooltip, valores predeterminados de SubContent, anatomía de select).
Modos
Progresivo (predeterminado). "Migrar accordion" = un componente, strangler-fig:
- Detecta primero el estado en progreso: un
<component>-base.tsxexistente, consumidores divididos entre imports viejos/nuevos. Los archivos SON el estado; reanuda, nunca reinicies. - Si el componente importa otros wrappers de ui que aún están en radix (select -> button), DETENTE y recomienda migrar esos primero, de abajo hacia arriba.
- Escribe la versión migrada en
<component>-base.tsx(original intacto; contenido del par dorado obtenido por URL, o transformado a mano, según la estrategia anterior); typecheck. Reapunta los consumidores UNO A LA VEZ (imports + las props del sitio de llamada enconsumer-props.md); typecheck cada uno. Cuando ningún consumidor importe el original: bórralo, renombra-base-> original, revierte los imports, verificación final, commit. Cuando el ÚLTIMO wrapper radix en el proyecto esté finalizado, cambiacomponents.jsonabase-<style>y elimina las dependencias radix.
Proyecto completo (solo cuando se pida explícitamente): el mismo trabajo por componente en orden de dependencia (wrappers hoja/compartidos como button y label primero). Después de los wrappers, barre TODO el código de la aplicación contra consumer-props.md — la superficie de rotura en los sitios de llamada es mucho mayor que asChild. Luego elimina las dependencias radix, instala, compilación completa.
Reglas estrictas
- NUNCA toques bibliotecas no Radix ni sus wrappers: cmdk (command), vaul (drawer), sonner, input-otp, react-day-picker (calendar), recharts (chart). Infórmalos como intencionalmente no tocados.
- Sin contraparte en Base UI: AspectRatio -> div con CSS aspect-ratio; Label -> etiqueta
<label>nativa; VisuallyHidden ->sr-only; Direction -> Direction Provider (propdirection, nodir). Popover Anchor y NavigationMenu Indicator no tienen equivalente: paso inerte + bandera. button.tsxmigra a la primitiva REAL de@base-ui/react/button, nunca un wrapper useRender hecho a mano.- Los cambios de comportamiento son MARCADOS, nunca parcheados en silencio (activación manual de tabs, elementos de menú que no se cierran al hacer clic, retardo de 50ms en nav-menu). El objetivo es un Base UI idiomático que coincida con el registro base de shadcn.
- Informe honesto: los archivos omitidos/revertidos se listan como marcados, nunca como migrados. Las fallas preexistentes se nombran como preexistentes.
Verificar e informar
Typecheck por archivo, build por lote, build completo al final contra la línea base.
Los informes residen en un directorio .migration/ en la raíz del proyecto, UN ARCHIVO POR COMPONENTE: .migration/<component>.md (ej. .migration/accordion.md). Reglas:
- Cada ejecución escribe (o sobrescribe completamente) el archivo para cada componente que migró. Volver a ejecutar un componente reemplaza su informe; nunca toques los archivos de otros componentes.
- Una ejecución de múltiples componentes ("migrar alert-dialog y dropdown-menu") escribe un archivo por componente, cada uno autocontenido; las notas compartidas de barrido de consumidores se repiten en cada archivo afectado.
- El modo proyecto completo escribe los archivos por componente más
.migration/project.md(intercambio de dependencias, resumen del barrido de código de aplicación, resultado final del build). - NO hay archivo de índice. El estado de la migración se deriva del disco, no se mantiene: escanea el directorio ui del proyecto (el alias
uide shadcn info, ej. components/ui o src/components/ui) en busca de imports radix restantes cuando se pregunte "qué queda". Termina cada resumen de ejecución con ese recuento derivado ("Quedan N wrappers en Radix").
Cada .migration/<component>.md usa EXACTAMENTE esta estructura (está documentada públicamente; los informes deben coincidir con ella):
# <component>
<fecha, estrategia utilizada (par dorado vía CLI / fusión / motor), veredicto de una línea>
## Cambiado
<cada archivo tocado, con qué cambió y por qué; incluye archivo:línea para cualquier cosa notable. Confirma que el barrido de restos está limpio: grep -n "radix-ui\|@radix-ui" en los archivos de este componente>
## Dejado intacto
<archivos que parecen relacionados pero no se tocaron intencionalmente, con la razón (cmdk/vaul/sonner no son radix; desviación no relacionada; etc.)>
## Cambios de comportamiento
<diferencias que compilan bien pero actúan de manera diferente; marcadas, nunca parcheadas (activación de tabs, cierre al hacer clic en menú, retardos...). Sección vacía si no hay ninguna>
## Verificar a mano
<lista de verificación QA manual breve para esta familia de primitivas: retorno de foco en diálogos, navegación por teclado + typeahead en menús/select, sensación de retardo en tooltip, eventos de commit de slider. Pasos concretos, un minuto de clics>