12k
All articles

shadcn/ui 已从 Radix 切换到 Base UI

shadcn/ui 将新项目默认从 Radix 切换到 Base UI。了解变化内容、哪些项目无需迁移,以及迁移的真实成本。

OpenReplay Team
OpenReplay Team
shadcn/ui 已从 Radix 切换到 Base UI

如果你的 shadcn/ui 应用运行在 Radix 上,你不需要迁移。Base UI 现在是新项目的默认选项,但 Radix 仍然获得完整支持,两个库都会持续收到更新,shadcn 团队自己的生产应用也仍在使用 Radix。

这次切换发生在 2026 年 7 月 3 日。此后的许多报道都暗示要进行一次重写,但更新日志中并没有这么说。

接下来该怎么做,取决于两个问题:你是否必须行动,以及如果选择迁移要付出多少成本。这个成本可以分为编译器能捕获的变更和它无法捕获的变更,而风险恰恰藏在后者之中。

核心要点

  • Base UI 是新建 shadcn/ui 项目的默认选项;Radix 并未被弃用,在全新 init 时使用 -b radix 即可保留 Radix。
  • 现有的 Radix 应用无需任何改动:两个库都会持续收到更新,shadcn 团队也仍在生产环境中使用 Radix。
  • 默认项发生变更,是因为 Base UI 已达到稳定的 1.6.0 版本、每周下载量超过六百万次,且 shadcn/create 的用户以二比一的比例选择它而非 Radix。
  • 迁移工作可分为破坏构建的变更(asChild 改为 render、Positioner/Popup、可为 null 的 Select 值)和静默的行为变更(手动激活的 Tabs、菜单保持打开),后者才是真正的成本所在。
  • 如果你要迁移,请同时保留两个库的安装,每次提交只迁移一个组件,并使用官方的 shadcn skill 而非 codemod。

2026 年 7 月 3 日改变了什么

新建的 shadcn/ui 项目现在默认使用 Base UI,而现有项目的其他一切都没有变化。有三处发生了改变:运行 npx shadcn init 时,除非你另行指定,否则会选择 Base UI;shadcn/create 将 Base UI 置于列表首位;组件文档现在默认打开 Base UI 标签页,Radix 标签页则位于其旁边。Radix 本身并未被弃用。

在新项目中继续使用 Radix 只需一个 flag:

pnpm dlx shadcn init -b radix

只要你的 CI 或脚手架脚本在无交互提示的情况下调用 shadcn init 并假定会得到 Radix,现在就应当加上这个 flag。这些脚本底层的默认项已经变了。

你需要迁移到 shadcn Base UI 组件吗?

不需要。更新日志承诺每一次更新和每一个新组件都会同时在两个库上发布,唯一的例外是那些仅存在于 Base UI、在 Radix 中没有对应实现的组件。日志中还提到,团队自己的生产代码仍在使用 Radix,且没有迁移计划。一个稳定的 Radix 应用没有强制时间表,没有弃用窗口,也没有维护断崖。本文余下的内容面向选择迁移的团队,而非必须迁移的团队。

默认项为什么变了?

更新日志给出了四个理由。该库已达到稳定的 1.6.0 版本,每周下载量突破六百万次。其维护者持续在增加实用的 primitive。shadcn 团队在所有新项目中已将其标准化。而在使用 shadcn/create 搭建项目的用户中,Base UI 与 Radix 的选择比例大约是二比一。

以上数据均引自更新日志。此后 Base UI 仍在持续发布:1.8.0 于 2026 年 9 月 4 日发布。更新日志还指出,Base UI 出自打造 Radix 的同一批人。需要安装的包是 @base-ui/react;旧的 @base-ui-components/react 名称在 npm 上已带有弃用提示,并重定向至新包。

迁移实际改变了什么

迁移中破坏构建的那一半是机械性的:都是编译器能立即捕获的重命名和类型变更。

RadixBase UI
trigger 上的 asChildrender prop
Portal > ContentPortal > Positioner > Popup
OverlayBackdrop
data-[state=open]: 变体data-open: 变体
onOpenChange(open) + event.preventDefault()onOpenChange(open, details) + details.cancel()

有一个细节:Positioner/Popup 的拆分只适用于带锚点的弹出层,例如 Menu、Select、Popover 和 Tooltip。Dialog 没有 Positioner;它由 Dialog.BackdropDialog.Popup 组成。

回调的变化如下:

// Radix: block closing via the event
onEscapeKeyDown={(event) => event.preventDefault()}

// Base UI: one callback, with a reason and a cancel()
onOpenChange={(open, details) => {
  if (details.reason === "escape-key") {
    details.cancel()
    return
  }
  setOpen(open)
}}

值的类型也发生了变化。受控的 Select 现在返回 Value | null 而非字符串,这使它成为最常见的构建报错来源:

const [fruit, setFruit] = useState<string | null>(null)

AccordionToggle Group 的值始终是数组,即使在单选模式下也是如此,因此 value="a" 要写成 value={["a"]}

那些能通过编译但行为不同的变更

迁移中危险的那一半能顺利通过类型检查,却依然改变了运行时行为。有三处差异尤为突出:

  • Tabs 默认为手动激活。 方向键会在各个 trigger 之间移动焦点,但不会切换可见的面板,除非你在 Tabs.List 上设置 activateOnFocus,其默认值为 false。Radix 的默认行为是自动激活。
  • Menu 的 checkbox 和 radio 项会让菜单保持打开。 Menu.CheckboxItemMenu.RadioItem 上的 closeOnClick 默认为 false,与 Radix 选中即关闭的行为相反。普通的 Menu.Item 仍默认为 true,因此行为会因 item 类型而异。
  • NavigationMenu 在 hover 时打开得更快。 Base UI 的 delay 默认为 50ms;Radix 的 delayDuration 默认为 200ms。用户此前只是划过而不会触发的菜单,现在会被打开。

这些都不会产生错误。点击后仍保持打开的菜单、忽略方向键的 tabs,对错误监控来说是不可见的,因为什么都没有抛出。这类回归问题会在 session replay 中显露出来:你会看到一位用户按下方向键,或者两次点击某个 checkbox 项,然后发现 UI 并未按其预期响应。

谁该迁移到 Base UI,谁不该?

如果你需要 Radix 从未提供的 Combobox、Autocomplete 或 Number Field,或者你希望与陆续发布的新 registry 组件保持一致,那就迁移。如果你的应用很稳定且上述情况都不适用,那就继续使用 Radix;支持承诺是明确的,一个正常运行的生产应用不会因为替换 primitive 而获得任何收益。

不要基于传闻中的组件缺失来做决定。Base UI 已提供 Context MenuToast,以及一个名为 Preview Card 的 hover-card primitive,而 shadcn 的 Base UI 文档涵盖了这三者。

如果要迁移,该怎么做

使用官方 skill,而不是 codemod,并且要渐进式推进。这个理由是站得住脚的:codemod 只了解文件的原始版本,因此它能处理你未改动过的组件,却会在你修改过的组件上失效。该 skill 会读取你实际拥有的代码,把你的修改一并迁移过来,并报告行为差异,而不是悄悄地把它们改写掉。

pnpm dlx skills add shadcn/ui

然后让你的 coding agent 迁移单个组件,例如 migrate accordion to base-ui。你可以同时保留两个库的安装,因此在各步骤之间构建始终是绿色的。每次运行都会对生成的产物做类型检查,将该组件的说明写入 .migration/ 目录,并作为独立的一次提交落到一个可随时丢弃的分支上。先从 button、label 这类叶子组件开始,再处理引用它们的组件,并在合并前阅读每份报告的行为变更部分。

一个警告:有些指南建议把 components.json 指向 Base UI,然后用 --overwrite 重新添加所有组件。这会丢弃你对这些文件所做的全部本地修改,而这恰恰是该 skill 存在的意义所要避免的失败。

结论

默认项变了;你的义务没有变。现有的 Radix 应用可以继续正常发布,新项目除非传入 -b radix 否则会使用 Base UI,而迁移是一个可选项目——它可见的成本是重命名,隐藏的成本是行为变化。如果你确实要迁移,请为那些静默差异预留 QA 时间,因为编译器在那里就不再帮得上忙了。先自己读一遍更新日志条目,然后判断 Combobox 或与 registry 保持一致是否值得开一个分支。

常见问题

与 Radix 相比,Base UI 是否已可用于生产环境?

是的。Base UI 于 2025 年 12 月以 '@base-ui/react' 包发布了稳定的 1.0.0 版本,此后持续稳定发版,并于 2026 年 9 月 4 日达到 1.8.0。它出自 Radix、Floating UI 和 Material UI 的创造者之手,其团队表示有意将 API 设计成与 Radix 相似,以便在两者之间迁移时更省力。shadcn 团队在其启动的每一个新项目中也都使用 Base UI。

Base UI 中所有的 value prop 都是数组吗?

不是。Accordion 的 'value' 始终是数组,Toggle Group 的 'value' 始终是字符串数组,即使在单选模式下也是如此;但 Tabs 的 'value' 仍是单个值,默认为 0,而 Select 的类型可以是单个值、数组或 null。请将 Accordion 和 Toggle Group 的值包装成数组,把受控 Select 的状态类型标注为可为 null,Tabs 则保持不变。

迁移到 Base UI 会影响那些从未使用 Radix 的 shadcn 组件吗?

不会。Command 封装的是 cmdk,Sonner 是独立的 toast 库,Calendar 使用 react-day-picker,Input OTP 使用 input-otp,Charts 基于 recharts。它们都不依赖 Radix primitive,因此从 Radix 到 Base UI 的迁移不会触及它们。只有那些 primitive 来自 Radix 的组件才需要改动,例如 Dialog、Menu、Select、Tabs 和 Popover。

shadcn 迁移 skill 需要 pnpm 吗?

不需要。更新日志分别给出了 pnpm、npm、yarn 和 bun 的 skill 安装方式,因此 'npx skills add shadcn/ui' 就是该 pnpm 命令的有效 npm 等价形式。安装完成后,该 skill 通过你的 coding agent 运行,每次迁移一个组件,并产出经过类型检查的代码、位于 '.migration' 目录下的按组件划分的报告,以及每个组件一次提交——这与包管理器无关。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.