shadcn/ui 默认改用 Base UI,Radix 被放弃了吗?

我最近在给一个新项目确定技术栈。

UI 这一项,我原本很自然地写成了:

bash 复制代码
shadcn/ui(Radix UI)

过去几年,这几乎已经成了一种固定搭配。提到 shadcn/ui,很多人首先想到的就是 Radix Primitives、Tailwind CSS,以及一套可以直接复制到项目里修改的组件代码。

但我后来发现,这个写法已经不完全准确了。

2026 年 7 月,shadcn/ui 把 Base UI 设成了新项目的默认底层组件库。组件文档默认展示 Base UI 版本,运行初始化命令时,默认生成的也不再是过去熟悉的 Radix 版本。

我看到这个变化时,第一反应和很多人差不多:

shadcn/ui 是不是准备放弃 Radix 了?

毕竟,一个项目把默认方案换掉,通常意味着它认为新的方案更适合未来。再加上 Base UI 和 Radix 做的事情看起来非常接近,很容易把这件事理解成一次替代。

我去看了一遍官方公告和两边的文档,最后发现事情没有这么简单。

改了默认项,不等于 Radix 被弃用

这次变化只影响新项目的默认选择。

如果还是想用 Radix,可以在初始化时明确指定:

csharp 复制代码
pnpm dlx shadcn init -b radix

已经使用 Radix 的项目也不会被自动切换。以后继续添加组件时,shadcn 仍然会按照项目原来的底层实现生成对应代码。

官方对这一点说得很明确:Radix 仍然完整支持,现有项目没有必要为了追随默认值而迁移。shadcn 自己也还有基于 Radix 的生产项目,而且没有计划把这些项目全部改成 Base UI。

这对我来说很重要。

因为 shadcn/ui 不是普通的黑盒组件依赖。它会把组件源码放进项目的 components/ui,开发者通常还会继续改样式、补属性,甚至调整组件结构。一个项目只要做过这些定制,所谓"迁移底层组件库"就绝不是改一下 package.json 那么简单。

Radix 中常见的 asChilddata-state、CSS 变量和事件行为,都可能已经进入项目自己的组件代码。仅仅因为新项目的默认项变了,就把这些代码全部重写,收益很难覆盖成本。

所以我现在的判断很简单:

旧项目该怎么跑就继续怎么跑。默认值的变化,主要是给新项目看的。

Base UI 为什么能成为默认

既然 Radix 没出什么严重问题,shadcn 为什么还是把 Base UI 放到了前面?

官方给出的几个数字很有意思。

在公告发布时,Base UI 已经到了 1.6.0,每周下载量超过 600 万。shadcn 团队新启动的项目都在使用 Base UI;在此前允许用户自由选择的 shadcn/create 中,新项目选择 Base UI 和 Radix 的比例大约是 2:1。

也就是说,shadcn 并不是突然决定强推一套没人用的新方案。它先让两套实现并存了一段时间,等自己的新项目和社区里的新项目都明显偏向 Base UI 以后,才顺势调整默认值。

我更在意的其实不是下载量,而是 Base UI 最近在补什么组件。

Radix 的 Dialog、Popover、Dropdown Menu、Tooltip 和 Select 已经非常成熟。这些也是它过去最吸引人的地方。但现在做一个完整的 SaaS、AI 产品或者管理后台,常用的交互已经不只是一组弹窗和菜单。

Combobox、Autocomplete、Number Field、Drawer、OTP Field,以及更完整的 Field 和 Form,都会频繁出现。Base UI 对这类现代应用组件的覆盖更积极,更新速度也更快。

这并不能证明 Radix 的组件不够好。更准确的说法是:

Radix 的优势是经典组件已经足够成熟;Base UI 的优势是还在快速向外扩展。

对于默认选项来说,shadcn 不只要考虑今天哪些组件稳定,也要考虑以后新增组件时,底层库能不能及时提供对应能力。

它们的差别,不只是换了一个名字

Base UI 不是凭空出现的竞争对手。

它的团队背景和 Radix、Material UI、Floating UI 都有联系,整体理念也非常接近:组件本身不预设视觉风格,但负责可访问性、焦点管理、键盘操作和复杂的交互状态,开发者再在上面建立自己的设计系统。

所以我更愿意把 Base UI 理解成一次带着已有经验重新设计的机会,而不是对 Radix 路线的否定。

两者最直观的区别之一,是组件组合方式。

Radix 常见的是 asChildDialogTrigger 不再额外渲染一个按钮,而是把触发行为交给里面的 Button

xml 复制代码
<DialogTrigger asChild>
  <Button>打开设置</Button>
</DialogTrigger>

Base UI 对应的写法是 render,要替换成什么元素,直接写在属性里:

xml 复制代码
<DialogTrigger render={<Button />}>
  打开设置
</DialogTrigger>

我一开始觉得,这好像只是把一个属性换成了另一个属性。但 Base UI 的 render 还可以使用函数形式,直接读取组件当前状态:

javascript 复制代码
<Switch.Thumb
  className={(state) =>
    state.checked ? "translate-x-5" : "translate-x-0"
  }
/>

状态样式的写法也不一样。Radix 组件里经常能看到:

ini 复制代码
className="data-[state=open]:animate-in"

Base UI 更常见的是:

ini 复制代码
className="data-open:animate-in"

这些代码都直接写在文章里,比截一张代码图片更容易看清,也方便读者复制。

当然,这些差异还没有大到足以让普通项目立刻迁移。如果业务代码主要使用 shadcn 封装好的 DialogSelectButton,两套实现的使用体验其实很接近。只有在修改 components/ui、开发复杂组件,或者自己做 Registry 时,底层 API 的差别才会明显暴露出来。

真正改变的,是 shadcn/ui 对自己的定义

查完这些资料后,我觉得最值得关注的并不是 Base UI 和 Radix 谁赢了。

真正变化的是 shadcn/ui 本身。

以前我会把它理解成 Radix 上面的一层组件代码和样式方案。这个理解在早期没有太大问题,因为绝大多数组件确实建立在 Radix 之上。

但现在,shadcn/ui 已经同时提供 Base UI、Radix,后来又加入了 React Aria 版本。它正在尽量让这些不同的底层实现,共用相似的组件名称、CLI、Registry、Blocks 和使用方式。

这意味着 shadcn/ui 想稳定下来的,不再是某一个 Primitive 库,而是它自己的组件分发体系。

底层可以是 Base UI,也可以是 Radix 或 React Aria;开发者最终拿到的,仍然是一份放在自己项目里、可以继续修改的组件源码。

所以,与其说 shadcn/ui 放弃了 Radix,不如说它正在摆脱另一个更旧的印象:

shadcn/ui 就等于 Radix UI。

这也是为什么我以后不会再只写 shadcn/ui,然后默认所有人都知道项目底层是什么。

技术栈文档里最好明确写成:

bash 复制代码
shadcn/ui(Base UI)

或者:

bash 复制代码
shadcn/ui(Radix UI)

对 AI 开发来说,这件事反而更麻烦

我现在很多项目都会让 AI 参与实现,因此这次变化还有一个很实际的影响。

AI 很容易把不同时期的 shadcn 代码混在一起。

它可能一边生成 Base UI 的组件,一边继续写 Radix 的:

arduino 复制代码
asChild
data-[state=open]
--radix-popover-content-available-height

也可能在原本的 Radix 项目里突然塞进:

ini 复制代码
render={<Button />}
data-open

这些代码单独看都可能是正确的,但放错项目以后,就会出现类型报错、状态样式失效,或者组件行为对不上文档的问题。

以前只写"使用 shadcn/ui"可能就够了。现在最好把底层实现也写进 PRD、技术架构和执行提示词里。

例如新项目可以明确要求:

bash 复制代码
UI 使用 shadcn/ui,底层采用 Base UI。
不要生成 Radix 专用的 asChild、data-state 或 Radix CSS 变量。

旧项目则反过来:

bash 复制代码
当前项目使用 shadcn/ui + Radix UI。
保持现有 Radix API,不主动迁移到 Base UI。

这可能比纠结两个库谁更先进更有用。因为大多数项目真正遇到的问题,并不是选择错了,而是选定以后又被混入了另一套写法。

我最后会怎么选

现在再开新项目,我会直接用 Base UI。不是因为 Radix 不能用,而是 shadcn/ui 已经把它设为默认,文档和后续组件都会更顺手。

已经用 Radix 的项目,我不会动。能正常跑,就没有必要为了换默认项再迁移一次。

以后我只会在技术栈里写清楚:shadcn/ui(Base UI),或者 shadcn/ui(Radix UI)。这样 AI 写代码时也不容易把两套 API 混在一起。

相关推荐
Vuji1 小时前
ReAct 与 Plan-Execute:两种 Agent 范式的实战对比
前端·agent
用户61595868000221 小时前
从零原生搭建一个微前端简易框架
前端
小月土星1 小时前
React + TypeScript 企业级开发实战:从类型约束到组件设计
前端
沙洲1 小时前
Vite 环境变量终极指南:从原理到企业级实战
前端
刘婉晴1 小时前
【Web漏洞】SQL 注入实战技巧
前端·数据库·sql
di24k24k2 小时前
多个 el-form 共用同一 ref 导致表单校验部分失效
前端·javascript·vue.js·elementui
NutShell Wang2 小时前
每帧重建整条路径、每秒倾倒 48MB 给 GC:实时折线图渲染架构的实测复盘
前端·性能优化·架构·图形渲染·数据可视化·vibe coding
小彤花园2 小时前
和 AI 结对写网站:从 JSON 到一整个工具集
前端·人工智能·程序员
RD_daoyi3 小时前
Google核心算法不再通知!全年持续滚动更新
大数据·服务器·前端·网络·搜索引擎·.net