shadcn/ui 为什么不是传统组件库

从 shadcn/ui 的源码分发思路出发,讨论它为什么不把自己当成传统 npm 组件库,以及复制源码、组件所有权、团队组件库和升级成本之间的取舍。

第一次看到 shadcn/ui 时,我的反应有点普通。

页面挺好看,组件也挺顺眼,但并没有那种“天降神器”的感觉。按钮、弹窗、表单、下拉框、卡片,这些东西 AntD、MUI、Arco、Semi 都有。单看视觉,shadcn/ui 甚至不算夸张,它没有端出一套庞大的设计帝国,更像把一批常用组件安静地摆在桌上。

真正有意思的是它的第一句话。

官网地址:https://ui.shadcn.com/

shadcn-ui组件库demo

它说自己不是一个组件库。

这句话看起来像营销话术,实际是整个思路的入口。传统组件库通常是这样用的:安装一个 npm 包,从包里 import 组件,然后等维护者继续发布版本。shadcn/ui 不是这个路子。它通过 CLI 把组件源码放进项目,代码落地后就是项目自己的代码。

组件不是租来的,是搬进仓库里的。

npm 组件库解决了复用,也带来了距离

传统组件库的好处很明显。

安装快,接入快,生态成熟,文档完善。一个团队不用从零写按钮、弹窗、日期选择器、表格和表单校验。尤其是后台系统,组件库就是生产工具。没有组件库,很多业务开发会退化成重复造轮子。

但 npm 组件库有一种天然距离。

代码在依赖包里,真正能改的是传参、插槽、样式覆盖和少量扩展点。只要需求没有越界,它很好用;一旦需求踩到组件库没准备好的地方,就开始别扭。

先是覆盖样式。

覆盖不了,就包一层。

包一层还不够,就写一堆例外。

例外多了以后,项目里长出一种很熟悉的景象:表面上使用统一组件库,实际每个复杂页面都在和组件库斗法。组件库越成熟,使用者越不敢轻易改;引用越广,维护者越不敢随便动。最后有些问题明知道是问题,也只能把它封成“兼容历史行为”。

软件里有很多东西不是不能改,而是改起来牵连太大。

组件库尤其如此。它本来是为了提高效率,后来也可能变成一个小型地形。所有页面都沿着它走,走得久了,路就不敢重修。

shadcn/ui 的关键是把所有权交回来

shadcn/ui 最巧的地方,不是又做了一批组件。

它换了分发方式。

shadcn-ui组件库介绍

用 CLI 添加一个组件,代码会进入项目目录。它通常基于 Radix 这类无样式或低样式的基础能力,再配合 Tailwind CSS、CSS variables 和一套约定好的结构。你拿到的不是黑盒组件,而是一份可以打开、阅读、修改、删减的源码。

这件事很重要。

因为代码一旦进了项目,责任关系就变了。

传统组件库像请外面的施工队。墙怎么砌、线怎么走,大体要按别人的标准来。shadcn/ui 更像把图纸和材料交给你,房子仍然要自己盖。自由多了,责任也多了。

这也是它说“不是组件库”的原因。它更像一个代码分发平台,一套组件样板,一种搭建自己组件库的起点。

官方文档后来也把这个方向说得更清楚:它强调 open code、composition 和 distribution。CLI 不只是加组件,还能处理 registry、preset、migrate、view、diff 这些围绕源码分发的事情。registry 也不只服务 React 组件,可以分发 hooks、页面、配置、规则和其他文件。

这就不只是“复制粘贴组件”了。

它把组件库从 npm 包,变成了一种可分发的代码资产。

复制不是低级,盲目复用才危险

程序员天然喜欢复用。

同样的逻辑写两遍,会不舒服;同样的组件复制两份,会觉得不专业。工程训练告诉我们要抽象、要封装、要 DRY。大多数时候,这是对的。

但复用不是神。

复用的前提,是变化方向足够一致。如果两个地方今天长得像,明天也会一起变化,那抽成一个组件很好。如果它们只是今天看起来像,背后的业务节奏、交互细节、权限、文案、状态都不一样,强行复用就会很痛。

最糟糕的复用,是把不同的东西绑在一起,然后用参数把差异一点点补回来。

一开始是 type

后来是 mode

再后来是 showExtraenableLegacyfromSpecialScene

最后组件像一只塞满纸条的抽屉,什么都能放,什么都不好找。

复制在这种时候反而干净。

复制的好处不是偷懒,而是让变化各归各位。这个页面的按钮要特殊,就改这个页面;这个表单要多一个状态,就改这个表单;等到几个地方真的长出稳定共性,再提炼回组件。先复制,后抽象,有时比先抽象,再被抽象反噬更稳。

shadcn/ui 把这个道理放到了组件分发上。

它没有假设所有项目都应该永远跟着一个 npm 包走。它默认每个项目最终都会长出自己的设计系统、自己的业务口味、自己的怪需求。既然迟早要改,那不如一开始就把源码给你。

它不是没有代价

shadcn/ui 很适合个人项目和中小项目。

拿来就能用,代码看得见,改起来不心虚。做一个 SaaS、一个后台、一个内容站、一个独立产品,常见组件很快就能搭起来。它不像大而全的组件库那样带来强烈视觉气味,也不会把项目绑死在某个庞大 API 上。

但它不是银弹。

第一,升级成本不会消失。

npm 组件库升级,至少形式上可以改版本号。虽然也可能踩坑,但路径很明确。shadcn/ui 的组件进了项目后,如果本地改过,再想跟进上游变化,就要看 diff,要判断哪些改动值得合并,哪些本地改法应该保留。CLI 能帮忙查看和应用,但不能替团队做判断。

第二,团队规范要自己管。

源码给了你,不代表组件库自然变好。项目里如果每个人都随手改一份 Button、Dialog、Form,最后也会变成另一种混乱。组件所有权交回来以后,团队需要更清楚地约定目录、命名、样式变量、可访问性、文档和评审规则。

第三,它不替代设计能力。

shadcn/ui 的默认组件好看,是因为它站在 Radix、Tailwind 和现代 Web 设计习惯上。但一个产品真正难的不是按钮圆角几像素,而是信息结构、状态设计、错误处理、权限边界、密度和可读性。组件只是木料,房子怎么住还得自己想。

第四,大团队未必适合完全照搬。

如果一个公司有多个产品线、统一品牌、统一设计语言、稳定设计团队和组件维护团队,以 npm 包形式发布的组件库仍然有价值。它能集中治理,统一升级,减少重复劳动,也能把设计系统当作组织资产维护。

所以问题不是 AntD 错了,shadcn/ui 对了。

问题是场景变了。

公司组件库仍然有意义

我以前做业务时,对组件库的感受很矛盾。

没有组件库,大家各写各的,页面很快散掉;组件库太重,又容易压住业务。尤其在公司里,一个组件一旦被很多项目引用,它就不再只是代码,而是组织协作的一部分。

这时 npm 包组件库有它的价值。

统一版本,统一发布,统一 changelog,统一兼容策略。设计团队可以围绕它推进规范,业务团队也能用同一套语言沟通。对大型组织来说,这种中心化并不是坏事,它能减少很多无意义的分叉。

但中心化最怕离业务太远。

组件库维护者如果只关心抽象的优雅,不关心业务页面真实怎么用,组件就会越来越像展品。看上去端庄,拿起来硌手。业务团队为了交付,只能在外面再包一层,包着包着,公司的统一组件库就成了底座,真正好用的东西散落在各业务仓库里。

shadcn/ui 提醒人的,正是这一点:

组件库不只是复用问题,也是所有权问题。

谁能改?谁负责?谁来判断变化该进公共层,还是留在业务层?谁承担升级成本?这些问题比“组件长什么样”更关键。

更适合 AI 时代的组件形态

还有一个现在越来越明显的变化:AI 写代码时,更喜欢看得见的代码。

如果组件逻辑藏在 npm 包里,AI 能看到的通常只是使用方式和类型定义。它可以帮你传参,可以帮你包一层,但很难真正理解组件细节。如果组件源码就在项目里,AI 就能读、能改、能跟着项目风格调整。

这让 shadcn/ui 在 AI 时代显得更顺手。

组件不再是远处的依赖,而是项目上下文的一部分。AI 可以看到 Button 怎么写,Form 怎么组织,Dialog 怎么封装,主题变量在哪里。它改出来的东西,更容易贴近项目本身。

这并不意味着所有依赖都要复制进仓库。

底层基础设施、复杂库、稳定协议,当然应该依赖成熟包。只是 UI 组件处在一个很特殊的位置:它离用户体验近,离业务变化近,离审美和品牌也近。越靠近这些变化,越需要可修改性。

shadcn/ui 正好卡在这个位置上。

它把底层复杂度交给 Radix 等成熟方案,把上层组件源码交给项目。既不完全从零造,也不完全依赖黑盒。

该怎么选

如果是个人项目、小团队产品、早期 SaaS、AI 辅助开发比较多的项目,我会优先考虑 shadcn/ui 这类源码分发方案。

原因很简单:改得动。

早期项目最怕不是组件不够完美,而是被不适合自己的抽象绑住。源码在手里,项目可以先跑起来,再慢慢长出自己的组件层。哪怕以后不再跟上游同步,也没什么大不了,代码本来就已经是自己的了。

如果是成熟公司、多产品线、大量业务共同使用一套设计体系,传统 npm 组件库仍然值得做。只是要警惕把组件库做成高高在上的东西。公共组件应该服务业务,而不是让业务围着组件库转。

更现实的做法可能是混合:

  1. 基础组件可以采用源码分发,允许项目按需改造。
  2. 设计变量、图标、品牌规范和可访问性要求保持统一。
  3. 业务组件不要太早上升到公共层,等多个场景真的稳定后再提炼。
  4. 升级不追求机械同步,而是看变化是否解决真实问题。
  5. 每个团队都要明确组件所有权,别让“复制”变成没人负责。

shadcn/ui 的流行,不是因为它发明了按钮。

它真正击中的,是很多前端项目长期存在的一种别扭:组件库提高了起步速度,也把修改权放远了。shadcn/ui 把修改权重新放回项目里,代价是团队要承担更多判断和维护责任。

这笔交易很公平。

代码世界里,很少有什么东西白送。传统组件库用依赖换效率,shadcn/ui 用源码换自由。选哪一个,不看口号,看项目要什么。

如果一个组件未来大概率会被业务反复改造,那让它一开始就属于项目,未必是坏事。