🔍 引子:两个信号
最近我在一个开源项目里用 Cursor 辅助开发 UI,发现一个现象:AI 几乎总是首推 Shadcn/ui。
需要表格 → Shadcn Table + TanStack 需要弹窗 → 直接给你 Dialog 源码 需要日期选择 → 组合 Popover + Calendar
即使我没指定,它也倾向用 Shadcn 的源语来回答。
起初我有些抗拒。公司项目里 Element Plus 的庞大 API 早已是肌肉记忆。但有一次让 AI 给表格加行内编辑,两次体验彻底改变了我对"组件库"这件事的认知。
✨ 第一次惊讶:AI 突然变准了
| 场景 | Element Plus | Shadcn/ui |
|---|---|---|
| AI 怎么改组件 | 对着黑盒硬猜 | 直接修改源码文件 |
| 幻觉情况 | 编造不存在的方法:editRow()、@row-dblclick |
精准插入状态切换逻辑 |
| 类型推导 | 混用 onMounted / onUpdated |
参数类型完全正确 |
| 根本原因 | AI 只看到 API 外壳 | AI 看到全部源码和状态流转 |
结论:对 AI 来说,源码是一等公民,API 文档只是二手信息。
🌪️ 第二次惊讶:样式开始漂移
几周后回顾整个项目,发现 UI 风格在悄悄分裂:
| 样式属性 | 开始前 | 几周后 |
|---|---|---|
| 圆角系统 | 统一 8px | 8px、4px、直角随机组合 |
| 卡片内边距 | 统一 p-6 |
p-4、p-5、p-6 交错出现 |
AI 每次都直接改 Tailwind 类名来满足琐碎需求------改按钮颜色、调弹窗宽度、加 4px 行高。
Shadcn 把样式控制权完全交了出来,但没保留任何约束的护栏。自由带来了精准修改的能力,也带来了设计系统的碎片化。
🎯 一个深层矛盾 + 八个隐藏问题
这两次经验暴露了当前组件库范式的深层矛盾:
传统库用黑盒换稳定性,把 AI 挡在门外;Shadcn 用白盒换可修改性,却牺牲了一致性。
但问题不止于此。把"组件库"这个物种放到 AI 编码的聚光灯下审视,会发现更多失效点------这些不是某个库的缺陷,而是整个品类设计假设的崩塌。
🥇 框架锁定:同一件事,N 套实现
| 现状 | 问题 | |
|---|---|---|
| React | antd、shadcn、mui |
各有 API,AI 记混 |
| Vue | element-plus、naive-ui |
跟 React 完全不通用 |
| Svelte | 贫瘠的生态 | 自己造轮子 |
同一种行为模式(下拉菜单、弹窗、表格),被重复实现了 N 次,每次绑定一个框架。AI 切换上下文时准确率急剧下降。
🥈 版本锁定:版本是给人类的,不是给 AI 的
yaml
Element Plus v1 → v2: 改了多少 API?没人记得清。
AI 的训练数据:混合了 v1 和 v2 的知识。
你的项目:装了 v2。
AI 写的代码:可能调的是 v1 的 API。
版本号把"规范的演进"和"实现的变更"绑在一起,AI 无法区分。
🥉 平台绑定:Web 会了,移动端从头学
| 行为:下拉选择 | 实现方式 |
|---|---|
| Web | div + 绝对定位 + 滚动列表 |
| 移动端 | 原生 Picker / Bottom Sheet |
| CLI | stdin/stdout 选项列表 |
同一交互语义,三种实现,三套组件库。AI 要跨平台,先学三套 API。
📱 设备不感知
桌面端卡片有 hover,触屏不需要。桌面端表格展示 20 列,移动端只能展示 3 列。
组件不会告诉 AI"我在手机上应该长什么样"。AI 只能猜------桌面端对了,移动端大概率错。
📋 行为规范缺位
组件库暴露的是代码:export function Select(props)。
不是行为规范:"按 ArrowDown 高亮下一个选项,按 Enter 选中,按 Escape 关闭。"
AI 能读代码,但它要的是设计意图------代码是"怎么做",AI 需要"做什么"和"为什么"。
🔍 隐式设计决策
| 值 | 为什么? | 在哪记录的? |
|---|---|---|
padding: 8px |
设计评审决定的 | 某人的脑子里 |
color: #2563eb |
品牌色 v3 | Figma 注释里 |
radius: 4px vs 8px |
等级区分 | PR review 里 |
代码本身不携带"为什么"。AI 修改时只能外部推断,推断错了就引入不一致。
📄 文档和测试的重复劳动
一个组件:
写代码 + 写文档 + 写 Storybook + 写测试 = 4 份离散工作
必须保持同步------但现实中从来不同步。如果 AI 已经生成了代码,它就具备了生成文档和测试所需的所有信息,但没人让它做。
♿ 无障碍是附加品,不是内建属性
每个组件库自己实现一次 ARIA。每个团队上线前再补一遍。AI 生成组件时,无障碍是最后才被想起的事。
如果规范本身就定义了"这个组件的 ARIA 角色是 menu",AI 一开始就能做对。
📊 全貌对比总结
| 维度 | 当前组件库 | AI 需要什么 |
|---|---|---|
| 框架 | 绑定一个,切换重学 | 行为独立于框架,生成时适配 |
| 版本 | 版本号捆绑一切 | 版本绑定规范,不绑定实现 |
| 平台 | Web 专属 | 一套规范,多平台生成 |
| 设备 | 桌面假设 | 设备差异显式声明 |
| 行为 | 代码即规范 | 规范独立于代码 |
| 决策 | 藏在讨论里 | 编码为可追溯的约束 |
| 文档/测试 | 手动维护,永远滞后 | 一次产出,多视图自动生成 |
| 无障碍 | 事后修补 | 规范内建,生成时保证 |
💡 代码越便宜,什么越贵?
AI 编码正在让代码的边际成本趋近于零。
写得快 ≠ 写得好。写得快 = 写得烂的速度也快。
当代码不再是稀缺资源,稀缺的东西变了:
| 越来越便宜 | 越来越贵 |
|---|---|
| 写一个组件 | 定义"什么是对的" |
| 复制粘贴 | 维护一致性 |
| 堆功能 | 守住约束和边界 |
| 修 bug | 保证正确性 |
| 写文档 | 保证文档跟代码一致 |
🏗️ 一个可能的架构:规范 → 生成
基于以上诊断,一个方向逐渐浮现:
把组件库从"代码包"重构为"规范描述器 + 生成引擎"
这不是"把组件拆成几层"(那是在旧框架里做优化),而是重新定义组件库的交付物。
演进路线图
bash
传统组件库 ──→ 源码级组件库 ──→ 规范驱动生成 ──→ 组件库消失
│ │ │ │
│ npm install │ 源码复制 │ Spec 驱动 │ 意图驱动
│ 黑盒 │ 白盒 │ 约束 AI │ AI 足够强
│ AI 猜 API │ 样式漂移 │ 规范=资产 │ 不需要约束
│ │ │ │
Element Plus Shadcn/ui 这个架构 未来?
架构示意图
typescript
┌──────────────────────────────────────────────────────────────────┐
│ 🧠 规范层(Spec) │
│ 唯一需要人工维护和版本化的东西 │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ 行为规范 │ │ 样式规范 │ │ 无障碍 │ │ 平台/设备 │ │
│ │ 状态定义 │ │ Token映射 │ │ ARIA角色 │ │ 适配规则 │ │
│ │ 事件流转 │ │ 约束规则 │ │ 键盘交互 │ │ 差异声明 │ │
│ └─────────────┘ └──────────┘ └──────────┘ └─────────────┘ │
└─────────────────────────┬────────────────────────────────────────┘
│ 读取
┌─────────────────────────▼────────────────────────────────────────┐
│ ⚙️ 生成引擎(Generator) │
│ AI 的工作,不是人的工作 │
│ │
│ 读取 Spec + 项目上下文 │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 输出: │ │
│ │ 📄 组件代码(.tsx / .vue / .svelte) │ │
│ │ 📝 类型定义 │ │
│ │ 🧪 测试用例 │ │
│ │ 📖 文档 / Storybook │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 切换平台/框架 = 换一个生成目标,Spec 不变 │
└─────────────────────────┬────────────────────────────────────────┘
│ 检查
┌─────────────────────────▼────────────────────────────────────────┐
│ 🛡️ 约束层(Guardrails) │
│ 确保 AI 的输出没有逾越边界 │
│ │
│ ✅ 样式必须引用 token,禁止硬编码 │
│ ✅ 无障碍要求必须满足,不通过不提交 │
│ ✅ 代码必须通过项目 lint / type check │
│ ✅ AI 不得修改规范层文件 │
└──────────────────────────────────────────────────────────────────┘
这个架构解决了什么?
| 问题 | ❌ 旧模式 | ✅ 新架构 |
|---|---|---|
| 框架锁定 | 一个框架一套库 | Spec 不变,生成引擎适配目标框架 |
| 版本锁定 | 升级库 = 全局风险 | 只有规范有版本,实现始终生成最新 |
| 平台绑定 | Web/移动端/CLI 各三套 | 一套 Spec,三个生成目标 |
| 设备不感知 | 组件不知道自己在哪运行 | Spec 中定义设备差异规则 |
| 行为规范缺位 | 代码里没有行为声明 | Spec 第一层就是行为定义 |
| 隐式设计决策 | 藏在评审讨论里 | Spec 明确记录设计约束 |
| 文档测试重复 | 四份离散工作各自同步 | 一次生成,四个输出视图 |
| 无障碍附加品 | 上线前补 | Spec 内建,生成时自动包含 |
📝 伪代码:规范 vs. 实现
以下展示"规范"和"生成"之间的关系------这不再是传统组件库的样子。
这是"组件库"------一个规范文件,不是代码
typescript
// 文件位置:@specs/dropdown-menu.ts
// 开发者维护此文件,AI 不可修改
export const dropdownMenuSpec = {
id: 'dropdown-menu',
version: '2.1',
// ── 行为规范 ──
states: {
isOpen: { type: 'boolean', default: false },
activeIndex: { type: 'number', default: -1 },
selectedValue: { type: 'string | null', default: null },
},
transitions: [
{ trigger: 'trigger:click', guard: '!isOpen', next: { isOpen: true } },
{ trigger: 'escape:keydown', guard: 'isOpen', next: { isOpen: false, activeIndex: -1 } },
{ trigger: 'arrowdown:keydown', guard: 'isOpen', next: { activeIndex: 'activeIndex + 1' } },
{ trigger: 'arrowup:keydown', guard: 'isOpen', next: { activeIndex: 'activeIndex - 1' } },
{ trigger: 'enter:keydown', guard: 'isOpen', next: { selectedValue: 'options[activeIndex]', isOpen: false } },
{ trigger: 'click-outside', guard: 'isOpen', next: { isOpen: false } },
],
// ── 样式规范 ──
styles: {
container: {
background: 'token.surface',
borderRadius: 'token.radius.md',
shadow: 'token.shadow.lg',
},
trigger: {
height: 'token.size.input',
padding: '0 token.spacing.md',
border: '1px solid token.border.default',
},
option: {
padding: 'token.spacing.sm token.spacing.md',
states: {
hover: { background: 'token.color.primary-50' },
selected: { background: 'token.color.primary', color: 'white' },
disabled: { opacity: '0.5' },
},
},
},
// ── 无障碍规范 ──
accessibility: {
container: { role: 'menu', orientation: 'vertical' },
trigger: { role: 'combobox', ariaHasPopup: 'menu', ariaExpanded: 'isOpen' },
option: { role: 'option', ariaSelected: 'is selected' },
keyboardNav: ['ArrowDown', 'ArrowUp', 'Enter', 'Escape', 'Home', 'End'],
focusManagement: 'restore trigger on close',
},
// ── 平台适配规则 ──
platform: {
web: { renderAs: 'div + absolute positioning' },
mobile: { renderAs: 'bottom-sheet', keyboardNavInapplicable: true },
cli: { renderAs: 'stdin list', selectedIndicator: '→' },
},
// ── 约束(AI 不可违反) ──
constraints: [
'所有样式值必须引用 token,不得硬编码',
'menu 关闭时必须将焦点归还 trigger',
'option 数量超过 10 时启用虚拟滚动',
'不得修改 spec 文件本身',
],
};
这是 AI 生成的组件------不是人写的
tsx
// 目标框架:React 18 + Tailwind
// 生成自:dropdownMenuSpec
import { useDropdownMenu } from '@headless/dropdown-menu';
import { tokens } from '@/lib/tokens';
import { useClickOutside } from '@/hooks/useClickOutside';
export function DropdownMenu({ options, triggerLabel }: Props) {
const { state, actions } = useDropdownMenu(dropdownMenuSpec);
const ref = useRef<HTMLDivElement>(null);
useClickOutside(ref, () => state.isOpen && actions.close());
return (
<div
ref={ref}
style={{
background: tokens.surface,
borderRadius: tokens.radius.md,
boxShadow: tokens.shadow.lg,
}}
role="menu"
aria-orientation="vertical"
>
<button
onClick={actions.toggle}
style={{
height: tokens.size.input,
padding: `0 ${tokens.spacing.md}`,
border: `1px solid ${tokens.border.default}`,
}}
role="combobox"
aria-haspopup="menu"
aria-expanded={state.isOpen}
>
{state.selectedValue ?? triggerLabel}
</button>
{state.isOpen && (
<div>
{options.map((opt, i) => (
<div
key={opt.value}
role="option"
aria-selected={i === state.activeIndex}
onClick={() => actions.select(opt.value)}
style={{
padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,
...(i === state.activeIndex && {
background: tokens.color.primary,
color: 'white',
}),
}}
>
{opt.label}
</div>
))}
</div>
)}
</div>
);
}
两段代码的关系是:规范 → 生成,不是"组件库 → 复制修改"。
⚠️ 这还不是终局
这个架构有一个隐含前提:我们还不够信任 AI,所以需要用规范文件来约束它。
如果某天模型强大到读一遍项目就能理解你的设计语言、代码风格、业务语义------它能从历史代码中自动推导出规范,从 PR review 中学习设计决策------那规范层也会消失。
scss
AI 能力
▲
│
没有组件库 ────┤────────────── ● (未来)
│
规范驱动生成 ────────┤────── ● (这个架构)
│
Shadcn 源码拷贝 ─────┤── ● (现在)
│
Element Plus npm 包 ────┤ ● (过去)
└──────────────────────► 时间
届时,"组件库"这个物种将不复存在。UI 不再是组装出来的,而是根据意图即时生成的。
但那不是今天。
🎯 今天的实用问题
今天的实用问题是:在一份规范能约束 AI 的时候,我们应该怎么设计这份规范?
上面的架构不是答案,是一个锚点。用来讨论:
| 问题 | 你的看法? |
|---|---|
| 规范应该写到什么粒度? | |
| 哪些东西必须被约束? | |
| 哪些应该交给 AI 自由发挥? | |
| 这套架构跟直接写代码比,是省了还是多了工作量? |
你怎么看?