2852 字
14 分钟
Shadcn/ui 与"反 npm"运动:为什么 2026 年最好的组件库就是没有组件库

一个反直觉的提议:“不要安装,直接复制”#

前端开发者的”职业病”之一就是——遇到问题先搜 npm。

需要日期选择器?npm install react-datepicker。需要表格?npm install react-table。需要图标?npm install lucide-react。不知不觉间,node_modules 膨胀到了 500MB,package.json 里的依赖列表比你的购物清单还长。

2026 年,Shadcn/ui 用一套完全相反的哲学改变了这一切:

“不要安装,复制粘贴就好。”

你没看错。这个 GitHub 上超过 10 万星的组件库,它的安装方式不是 npm install,而是让你在终端跑一个命令,把组件的源代码直接复制到你的项目里:

Terminal window
# 不是 npm install @shadcn/ui
# 而是:
$ npx shadcn-ui@latest add button
# 在 src/components/ui/button.tsx 中创建了一个文件
# ✅ 完全可控
# ✅ 可随意修改
# ✅ 没有运行时依赖
# ✅ 不污染 node_modules

为什么”反 npm”成了一个趋势?#

这个看似”倒退”的做法,实际上解决了好几个前端开发的世纪难题:

问题一:依赖地狱#

传统组件库(如 Material UI、Ant Design)是一个大而全的黑盒。你只需要一个 Button 组件,但 npm install 后你得到了整个组件库——几百个组件、几十个依赖包、加起来几十 MB 的代码。而且一旦某个深层依赖出了安全漏洞,你就得跟着升级。

Shadcn/ui 的做法是:每个组件是独立的。你只复制你需要的组件,没有任何隐藏依赖。

// 传统组件库的 package.json
{
"dependencies": {
"@mui/material": "^5.15.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
// 加上 peer dependencies... 光是一个 button 就引入了几十个包
}
}
// 使用 Shadcn/ui 后的 package.json
{
"dependencies": {
"class-variance-authority": "^0.7.0", // 唯一的核心依赖
"tailwind-merge": "^2.2.0"
// 组件的样式来自 Tailwind,布局来自你自己的代码
}
}

问题二:定制困难#

用传统组件库的开发者大概都经历过这种崩溃——“我想把 Button 的圆角从 8px 改成 12px”——然后发现必须用 styled() 包裹一层,再然后发现主题系统里有个奇怪的优先级规则,最后不得不加上 !important 才能覆盖默认样式。

Shadcn/ui 的组件就是你的代码。想改圆角?直接找到 button.tsx,找到 rounded-lg 改成 rounded-xl,完事。

src/components/ui/button.tsx
// 这是你的代码,你想怎么改就怎么改
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-xl px-3 text-xs",
lg: "h-10 rounded-xl px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
// 完整源代码在你的项目中,你想加什么功能就加什么
export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}

问题三:版本锁定#

“这个组件库 4.0 改了 API,我们的项目要升级吗?“——传统组件库升级永远是一个”牵一发而动全身”的工程。一个 Button 组件的样式变化可能影响全站上下几十个页面的 UI。

Shadcn/ui 没有版本号的概念。你复制的是”当前时刻的最佳实现”。下次想更新?手动对比下 diff,或者直接重新 npx add 覆盖。每个组件都有自己的版本节奏。

Shadcn/Universal:跨框架的终极组件库#

Shadcn 模式在 2026 年已经超越了 React 的边界。社区项目 Shadcn-Universal 用同样的哲学,实现了跨框架组件:

Terminal window
# 为 Vue 项目添加 Shadcn 组件
$ npx shadcn-universal add --framework vue button
# ✅ 生成 src/components/ui/button.vue
# 为 Svelte 项目添加
$ npx shadcn-universal add --framework svelte button
# ✅ 生成 src/lib/components/ui/button.svelte
# 为 Flutter 项目添加
$ npx shadcn-universal add --framework flutter button
# ✅ 生成 lib/components/ui/button.dart

同一套设计语言,同样的组件 API,在不同的框架中都用原生方式实现。这才是”跨平台”的正确打开方式——不是套个 WebView 或者搞个运行时适配层,而是每个平台都生成该平台的惯用代码

2026 年的组件生态#

Shadcn/ui 的成功催生了整个”复制粘贴”组件生态:

  • Magic UI:动画组件库,复制即用,零运行时依赖
  • Aceternity UI:炫酷的交互组件集合
  • Tremor:Dashboard 组件,完全复制粘贴模式
  • Park UI:基于 Ark UI 的跨框架无头组件

每个组件都是”你的代码”,而不是”别人的依赖”。

Terminal window
# 2026 年前端项目的典型结构
my-app/
├── node_modules/ # 只包含基础设施(框架、工具链)
├── src/
├── components/
├── ui/ # 从组件库"复制"过来的组件
├── button.tsx
├── card.tsx
├── dialog.tsx
└── table.tsx
└── features/ # 业务组件
└── ...

看见了吗?UI 组件目录和业务组件目录是同一级的。因为从 Shadcn/ui “安装”的组件本来就是你项目代码的一部分——和你的业务组件没有任何区别。

Tailwind CSS v5:复制粘贴模式的幕后推手#

Shadcn/ui 能成功,很大程度上要归功于 Tailwind CSS。2026 年 Tailwind CSS v5 已经发布,带来了几个关键改进:

  1. 原生 CSS 嵌套支持:不再需要 PostCSS 嵌套插件
  2. 性能提升 10 倍:新的引擎基于 Rust 重新实现
  3. 更好的 JIT 编译:开发模式下热更新几乎无感知
/* Tailwind v5 + 原生 CSS 嵌套 */
@layer components {
.card {
@apply rounded-xl border bg-card text-card-foreground shadow-sm;
.card-header {
@apply flex flex-col space-y-1.5 p-6;
}
.card-title {
@apply text-2xl font-semibold leading-none tracking-tight;
}
.card-content {
@apply p-6 pt-0;
}
}
}

Tailwind + Shadcn/ui 的组合,让开发者可以像搭乐高一样组装 UI——每个组件都是经过精心设计的”积木块”,你可以自由组合、修改、替换,而不需要关心”这个组件内部用了什么 CSS 类库”。

2026 年前端架构的终极问题:什么时候用客户端渲染?#

聊完了 Shadcn/ui 和 Tailwind,我们来讨论一个更本质的问题:2026 年,应该怎么决定页面的渲染策略?

答案取决于你的页面类型。我把常见的渲染策略归纳为四种模式:

纯静态(SSG):适合博客、文档、营销页面。构建时生成 HTML,CDN 直接分发。Astro 和 Next.js 的 static export 是主流选择。

服务端渲染(SSR):适合需要 SEO 的动态页面(电商、内容平台)。Next.js 和 Remix 的默认模式。

流式 SSR(Streaming):适合有大量数据获取的页面。页面外壳先发到浏览器,内容 chunks 逐步到达。2026 年这已经是 Next.js 的默认选项。

完全客户端渲染(CSR):适合后台管理面板、Dashboard、交互式应用。Vite + React 是起步最快的组合。

// Next.js 2026 中的混合渲染策略
import { Metadata } from 'next'
// 页面级别的渲染策略配置
export const metadata: Metadata = {
title: '商品详情页'
}
// 静态部分——构建时生成
async function ProductInfo({ id }: { id: string }) {
const product = await cache(
() => db.product.findUnique({ where: { id } }),
['product', id],
{ revalidate: 3600 } // ISR: 每小时重新验证
)
return <ProductCard product={product} />
}
// 动态部分——每次请求时服务端渲染
async function RealTimeStock({ id }: { id: string }) {
const stock = await db.inventory.getCurrentStock(id)
// 这个组件不会缓存
return <StockBadge count={stock} />
}
// 互动部分——客户端水合
function AddToCartButton({ productId }: { productId: string }) {
'use client'
const [added, setAdded] = useState(false)
return (
<button onClick={() => {
addToCart(productId)
setAdded(true)
}}>
{added ? '已加入购物车 ✓' : '加入购物车'}
</button>
)
}

同一个页面里,静态部分(商品信息)每小时重新验证一次缓存,动态部分(库存)每次请求都获取最新数据,互动部分(按钮)只在客户端加载 JS。三种策略在同一个组件树里无缝协作——这就是 2026 年前端渲染的成熟形态。

实战对比:传统组件库 vs Shadcn/ui#

为了让你更直观地感受差别,我用一个”修改按钮颜色”的例子做对比:

使用 Ant Design(传统方式)

// 1. 先找到主题配置的文档(翻文档 10 分钟)
// 2. 写 ConfigProvider 包裹
import { ConfigProvider, Button } from 'antd'
// 3. 或者用 styled-components 覆盖
const StyledButton = styled(Button)`
&&& {
background: #ff6b6b;
border-color: #ff6b6b;
&:hover {
background: #ff8787;
}
}
`
// 4. 担心升级后样式不对(经常发生)

使用 Shadcn/ui(复制粘贴模式)

// 1. 直接找到 button.tsx
// 2. 改一个 className
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-xl text-sm font-medium transition-colors",
{
variants: {
variant: {
default: "bg-[#ff6b6b] text-white shadow hover:bg-[#ff8787]", // ← 直接改这里
// ...
}
}
}
)
// 完事。不需要包裹、不需要覆盖、不需要担心升级。
// 因为这就是你的代码!

前一种方式需要 4 步操作,涉及主题系统、样式覆盖、版本兼容等多层抽象。后者只需要 1 步——找到对应文件,修改一行代码。这就是”复制粘贴”模式最大的优势:简单直接。

这个模式会走向哪里?#

Shadcn/ui 的成功正在催生整个”复制粘贴生态系统”。2026 年,你会发现很多新的工具和框架都在采用类似的哲学:

  • Park UI:基于 Ark UI 的无头组件,但以 Shadcn 风格分发
  • Magic UI:动画组件,复制即用
  • Tremor:Dashboard 组件集合,完全复制粘贴模式
  • Degen:社区贡献的”奇葩”组件集(旋转的 Toast、弹跳的按钮——只为你真的需要它们)

甚至连 VS Code 的 Snippet 系统 也在被重新思考。为什么不直接把常用组件的源代码做成 snippet?当你想用 DatePicker 的时候,直接输入 datepicker 回车,完整源代码就出现在你的编辑器里了——不需要 npm install,不需要等待下载,不需要依赖冲突。

给新手的建议#

如果你刚开始接触前端开发,我的建议是:直接从 Shadcn/ui 起步。不是说让你不要学 CSS 基础,而是 Shadcn/ui 的组件本身就是很好的学习材料——所有源代码都摆在你面前,没有黑盒,每一行样式都可以追溯和理解。

打开一个 button.tsx,看看 cva 函数的用法,看看 Tailwind class 的组合方式,看看 Slot 组件是怎么实现 asChild 属性的——你学到的是真正的”前端技能”,而不是”某个组件库的用法”。

这种学习方式比看任何教程都来得直接:代码就在你面前,改一行看一次效果,20 分钟后你就对这个组件了如指掌了。

总结#

Shadcn/ui 和”复制粘贴”模式的兴起,本质上是前端社区对过度抽象的一次反拨。

过去十年,前端开发一直在”封装 - 安装 - 升级”的循环中打转。每一层抽象都声称能”提升效率”,但实际上每个抽象层都带来了额外的学习成本和维护负担。

Shadcn/ui 的革命性在于:它告诉开发者 “这些组件就是你的,不用通过我”。没有黑盒、没有版本锁定、没有升级痛苦的组件模式——这才是 2026 年前端开发的正确打开方式。

Shadcn/ui 与"反 npm"运动:为什么 2026 年最好的组件库就是没有组件库
https://www.oferry.com/posts/a135/
作者
晨平安
发布于
2026-06-04
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00