Communitygithub.com

shadcn-ui/ui

将React项目从Radix UI迁移到Base UI。当需要从radix迁移、转换到base-ui、转换radix原语或切换shadcn项目的底层库时使用。支持单个组件(如“迁移accordion”)和整个项目。

ui 是什么?

该技能用于自动化将React项目(特别是使用shadcn/ui构建的项目)从Radix UI迁移到Base UI的过程。它主要处理shadcn包装器、手写的radix组合代码及其消费者代码。技能的核心策略是“黄金对”优先,利用shadcn CLI(对于已知风格的radix项目)获取原始的和迁移后的组件变体,然后通过三路合并(git merge-file)将用户的定制迁移到新的Base UI变体上。对于没有Base UI对应风格的旧版样式(如new-york),则使用转换引擎在原文件上重写Radix原语,同时保留用户的确切样式。该技能支持渐进式(组件级)和全项目迁移两种模式,并严格要求:每次修改前保障Git工作目录干净、按组件逐个提交、对每个迁移后的文件进行Radix残留检查、生成标准化的迁移报告。它不会触碰非Radix库(如cmdk, vaul, sonner),并在没有Base UI对应项时会标记行为差异,而非静默修复。最终目标是在保持项目始终可构建的前提下,完整、可审计地将项目迁移至Base UI。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/shadcn-ui/ui/tree/HEAD/skills/migrate-radix-to-base

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Radix UI -> Base UI 迁移指南

将 shadcn 包装器、手写的 radix 组合组件及其消费者代码迁移至 @base-ui/react,并保证项目在每一步都可构建。请精确操作;切勿猜测映射关系。当某个属性或部件不在以下参考文件中时,请在转换前检查 node_modules/@base-ui/react/**/*.d.ts,并在报告中记录缺失项。

预检步骤(始终执行)

  1. 运行 npx shadcn@latest info --json(或项目所用的运行器):获取当前基础库、STYLE(例如 radix-lyra)、tailwind 版本、别名、已安装的组件以及包管理器。信任此输出,而非推断。
  2. 检测包管理器(packageManager 字段 / 锁文件:pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json),并使用它执行所有安装操作。绝不遗留过时的锁文件。
  3. 要求干净的 git 工作树;在分支上工作;每个组件一个提交。
  4. 在接触依赖项之前进行基线检查:运行项目的类型检查/构建,确保预先存在的失败不会归咎于你。
  5. 将 @base-ui/react 与 radix 一同安装。仅当最后一个组件迁移完成后才移除 Radix 包(两者可共存)。

策略:优先使用黄金对,其次使用转换引擎

  • 通过 CLI 获取黄金对(首选)。 如果项目是 shadcn 且具有已知风格(radix-<style>),则 shadcn CLI 本身就是黄金对执行器:
    1. 首先对每个 ui 包装器进行分类:将用户的文件与其源文件进行差异比较,使用 components.json 中的风格原样拼入 URL(https://ui.shadcn.com/r/styles/<style>/<component>.json,files[0].content)。这适用于带前缀的风格(如 radix-nova)以及旧版无前缀的风格(如 new-york, new-york-v4, default),这些仍被服务。
    2. 全项目模式:立即将 components.json 中的风格从 radix-<style> 翻转为 base-<style>。渐进模式:不要立即翻转(项目大部分仍是 radix;翻转仅在最后一个组件迁移后执行一次);改为直接通过 URL 获取 base 变体(https://ui.shadcn.com/r/styles/base-<style>/<component>.json)。
    3. 未定制包装器,全项目模式:运行 shadcn add <component> --overwrite,它将使用项目确切的图标/字体/预设解析交付 base 变体。切勿使用 --all --overwrite;应逐个组件进行,否则会被无关的注册表版本漂移淹没。渐进模式:切勿使用 --overwrite(它会破坏消费者仍在导入的原始文件);改为将获取的 base 变体内容写入 <component>-base.tsx。
    4. 定制包装器:获取 base 变体并将用户的差异重放上去(他们的定制必须保留;--overwrite 会破坏它们)。可规模化实施的机械方法:git merge-file user.tsx radix-golden.tsx base-golden.tsx(三路合并,以 radix 黄金对为祖先),可自动解决大多数文件;使用参考表手动解决冲突。
    5. 在每个黄金对文件(包括“干净”合并的文件)上强制进行残留扫描:grep -n "radix-ui\|@radix-ui\|IconPlaceholder" 每个文件。注册表有时会在变体之间重新排列函数,这可能导致三路合并报告零冲突,同时留下陈旧的 radix 代码段。干净的合并并不证明文件是干净的。 该策略比重建转换更可靠;只要黄金对存在,就使用它。消费者/应用代码没有 CLI 机制:始终根据 consumer-props.md 手动迁移。
  • 旧版样式(new-york, new-york-v4, default):仅分类,不重放。 这些样式没有 base 对应项(不存在 base-new-york),将其重定向到 base- 变体会改变用户应用的外观。仅使用 radix 黄金对来检测定制,然后对用户自己的文件运行转换引擎:重写原语,保留其确切的类,应用类映射重命名。用户的外观保持不变。在旧版全项目迁移结束时,标记(不要修复):风格名称对 CLI 来说仍然读作 radix,因此将来的 shadcn add 将交付 radix 变体;用户决定是切换风格还是手动添加。
  • 转换引擎(备用方案)。 对于手写的 radix 代码、非 shadcn 项目、未知风格:使用 universal-patterns.md(两种形式的导入:radix-ui 和 @radix-ui/react-*;asChild -> render 及工作示例;Portal > Positioner > Popup;positioner FORWARD 规则;部件重命名)、按族分类的属性表(overlays.md, menus.md, form-controls.md, disclosure.md, display-misc.md)、用于 data-attribute/CSS-var 重写的 class-mapping.md,以及 wrapper-shapes.md 获取精确的目标形状(tooltip arrow, SubContent 默认值, select 解剖结构)。

模式

渐进式(默认)。 命令如“迁移 accordion”= 单个组件,采用绞杀者模式:

  1. 首先检测进行中的状态:是否存在 <component>-base.tsx,消费者是否在旧/新导入之间拆分。文件本身就是状态;继续,绝不重启。
  2. 如果该组件导入其他仍在使用 radix 的 ui 包装器(例如 select -> button),则停止并建议先迁移那些底层包装器。
  3. 将迁移后的版本写入 <component>-base.tsx(原始文件保持不变;根据上述策略,黄金对内容通过 URL 获取或手动转换);进行类型检查。一次一个地重指消费者(导入 + consumer-props.md 中的调用点属性);每次进行类型检查。当没有消费者再导入原始文件时:删除它,将 -base 重命名为原始名称,将导入翻转回来,进行最终检查,提交。当项目中最后一个 radix 包装器完成时,将 components.json 翻转为 base-<style> 并移除 radix 依赖。

全项目模式(仅当明确要求时):按依赖顺序执行相同组件级工作(叶子/共享包装器如 button 和 label 优先)。包装器完成后,根据 consumer-props.md 扫描所有应用代码——调用点的破坏范围比 asChild 大得多。然后移除 radix 依赖,安装,完整构建。

硬性规则

  • 绝不触碰非 radix 库或其包装器:cmdk(command), vaul(drawer), sonner, input-otp, react-day-picker(calendar), recharts(chart)。报告它们为故意未触碰。
  • 无 Base UI 对应项:AspectRatio -> CSS aspect-ratio div;Label -> 原生 <label>;VisuallyHidden -> sr-only;Direction -> Direction Provider(使用 direction 属性,而非 dir)。Popover Anchor 和 NavigationMenu Indicator 没有等价物:惰性传递 + 标记。
  • button.tsx 迁移至真正的 @base-ui/react/button 原语,而非手写的 useRender 包装器。
  • 行为差异标记,而非静默修复(例如 tabs 手动激活、菜单项点击不关闭、nav-menu 50ms 延迟)。目标是符合 shadcn base 注册表的惯用 Base UI。
  • 诚实报告:跳过/还原的文件列为已标记,而非已迁移。预先存在的失败应标明为预先存在。

验证与报告

每个文件进行类型检查,每批进行构建,最后进行完整构建并与基线比较。

报告存放在项目根目录下的 .migration/ 目录中,每个组件一个文件:.migration/<component>.md(例如 .migration/accordion.md)。 规则:

  • 每次运行都会写入(或完全覆盖)其所迁移的每个组件的文件。重新运行某个组件会替换其报告;绝不触碰其他组件的文件。
  • 一次多组件运行(如“迁移 alert-dialog 和 dropdown-menu”)会为每个组件写入一个文件,每个文件独立;共享的消费者扫描说明将在每个受影响的文件中重复。
  • 全项目模式除了每个组件的文件外,还会写入 .migration/project.md(依赖交换、应用代码扫描摘要、最终构建结果)。
  • 没有索引文件。 迁移状态来源于磁盘,而非维护:当被问及“还剩什么”时,扫描项目的 ui 目录(来自 shadcn info 的 ui 别名,例如 components/ui 或 src/components/ui)以查找剩余的 radix 导入。每次运行总结时输出该派生计数(“N 个包装器仍在使用 Radix”)。

每个 .migration/<component>.md 严格使用以下结构(该结构公开记录;报告必须匹配):

# <component>

<日期,使用的策略(通过 CLI 的黄金对 / 合并 / 引擎),一行结论>

## 更改的

<每个接触过的文件,更改了什么以及原因;对任何值得注意的内容包含 file:line。确认残留扫描干净:
对此组件的文件执行 grep -n "radix-ui\|@radix-ui">

## 保留的

<看起来相关但有意未触碰的文件,以及原因(cmdk/vaul/sonner 不是 radix;无关漂移等)>

## 行为变更

<编译通过但行为不同的差异;已标记,未静默修复(tabs 激活、菜单点击关闭、延迟...)。若无则此节为空>

## 手动验证

<此原语族的简短手动 QA 检查清单:对话框的焦点返回、菜单/选择上的键盘导航+类型搜索、tooltip 延迟感受、slider 提交事件。具体步骤,大约一分钟点击操作>

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能