AI 生成 UI 的审查清单
截图好看,不等于能用
上个月我接手了一个用 v0.dev 生成的后台管理面板。在 AI 预览窗口里,每张截图都堪称教科书级——间距均匀、层级清晰、组件现代化。我把代码拉进项目,接上真实 API,切到移动端一看:长用户名把头像挤出了视口,德语文案撑爆了按钮,空数据状态下整张卡片塌成一条线,Tab 键在自定义下拉框里直接消失了。
这不是个例。CodeRabbit 对 470 个真实 GitHub PR 的分析表明,AI 生成代码的问题率约为人类手写代码的 1.7 倍,其中逻辑和正确性错误高出 75%,可读性问题超过 3 倍,错误处理缺失接近 2 倍[1]。Master.dev 的可访问性基准测试更直接:主流 LLM 生成 UI 代码的默认可访问性通过率只有 12%[2]。
问题不在 AI 工具本身——它擅长的是在给定约束下生成视觉上合理的代码。问题在于,我们太容易把「预览窗口里好看」等同于「生产环境里可用」。这两者之间,隔着一份系统性的审查清单。
AI 生成 UI 为什么会「好看但不好用」
理解问题之前,需要搞清楚 AI 生成 UI 的内在局限。
视觉优先的训练偏差。 LLM 的训练数据里,GitHub 上的 React 代码大量使用 <div> + CSS 类名构建界面,语义化 HTML 和 ARIA 属性的代表性严重不足。模型学到的是「代码看起来像什么」,而不是「代码对辅助技术意味着什么」[2]。一个 29 行的侧边栏组件,可以包含 10 个不同的可访问性失败——但在截图里完全看不出来。
Token 经济学的副作用。 <div onClick={fn}> 比一个带完整 ARIA 属性、键盘事件和焦点管理的 <button> 消耗更少的 token。在模型的统计优化目标下,更短的代码路径天然占优,可访问性成了被「优化」掉的那一层。
缺乏可访问性树模型。 浏览器在渲染页面时会构建两棵树:渲染树决定屏幕画什么,可访问性树决定屏幕阅读器读什么。CSS 能让一个 <div> 看起来像按钮,但只有 HTML 语义能让它「成为」按钮。当前的 LLM 没有可访问性树的内部表示,它们生成的是像素层面的正确,不是语义层面的正确[2]。
上下文断裂。 AI 不知道你的项目用了几种语言、数据最长的用户名有多少字符、移动端断点设在哪里。它只能在 prompt 给定的信息范围内生成「统计上合理」的代码,而不是「上下文里正确」的代码[1]。
下面这张表总结了 AI 生成 UI 的典型失败模式:
| 失败模式 | 表现 | 根因 | 发现时机 |
|---|---|---|---|
| 视觉正确但语义缺失 | <div> 充当按钮,屏幕阅读器无法识别 | 训练数据偏差 + Token 经济学 | 可访问性审计 |
| 静态完美但动态崩溃 | 空数据、加载中、错误状态布局错乱 | 只生成理想路径代码 | 真实数据测试 |
| 英文正常但多语言溢出 | 德语、法语按钮文案超出容器 | 缺乏 i18n 上下文 | 国际化测试 |
| 桌面端正常但移动端破碎 | 固定宽度卡片超出视口 | 默认桌面优先、缺乏响应式约束 | 响应式测试 |
| 正常路径流畅但交互断裂 | 键盘无法到达自定义组件 | 缺乏键盘交互模型 | 键盘导航测试 |
案例一:长文本撑爆卡片布局
场景
用 AI 生成一个用户资料卡片组件,包含头像、用户名、职位和公司名。Prompt 里给的示例数据是 "Jane Smith"、"Product Designer"。
翻车
上线后,用户「阿布都热合木·麦麦提艾力」的名字直接溢出卡片边界,把右侧的操作按钮挤到了下一行。更糟的是,当用户设置了长公司名(比如「某某科技(深圳)有限公司前海分公司」),整张卡片的高度翻倍,和相邻卡片的网格对齐全乱了。
诊断
AI 生成的 CSS 对所有文本容器使用了固定的 height 和 white-space: nowrap,没有考虑文本截断或弹性布局。头像尺寸用了固定像素,和文字之间没有弹性间距。
修复
// ❌ AI 生成的做法:固定尺寸 + 不截断
const UserCard = ({ user }) => (
<div className="w-[320px] h-[120px] flex items-center gap-3">
<img src={user.avatar} className="w-12 h-12 rounded-full" />
<div>
{/* 长文本直接溢出,没有截断机制 */}
<p className="text-base font-medium">{user.name}</p>
<p className="text-sm text-gray-500">{user.company}</p>
</div>
</div>
)// ✅ 修复后的做法:弹性布局 + 文本截断 + 最小尺寸保障
const UserCard = ({ user }) => (
<div className="w-full min-w-0 max-w-[320px] min-h-[120px] flex items-start gap-3 p-4">
<img
src={user.avatar}
className="w-12 h-12 rounded-full shrink-0"
alt={`${user.name}的头像`}
/>
{/* min-w-0 是 flex 子元素文本截断的前提 */}
<div className="min-w-0 flex-1">
<p className="text-base font-medium truncate" title={user.name}>
{user.name}
</p>
<p className="text-sm text-gray-500 line-clamp-2" title={user.company}>
{user.company}
</p>
</div>
</div>
)差异点:
- 容器宽度:从固定
w-[320px]改为w-full max-w-[320px],允许在窄屏下收缩 - 文本截断:用户名用
truncate(单行省略),公司名用line-clamp-2(两行省略),都保留title属性供 hover 查看完整内容 - Flex 子元素陷阱:
min-w-0是 flex 布局中文本截断生效的前提,shrink-0防止头像被压缩 - 高度策略:从固定
h-[120px]改为min-h-[120px],允许长文本撑高但不低于基准值
案例二:可访问性在 AI 生成的组件里集体缺席
场景
AI 生成了一组仪表盘组件:侧边栏导航、数据表格、筛选器和模态框。视觉上与 Figma 设计稿高度一致。
翻车
可访问性审计发现,一个 29 行的侧边栏组件包含 10 个可访问性失败:导航区域缺少 <nav> 地标、分组标题用 <div> 而非 <h2>、折叠按钮的 chevron 旋转只是视觉效果没有 aria-expanded、所有可点击的 <div> 不在 Tab 序列中[2]。
诊断
这是 AI 生成 UI 最普遍的问题。模型生成了视觉上正确的代码,但语义层面几乎为零。问题不在于 AI「不知道」可访问性——如果你在 prompt 里明确要求,它能生成带 ARIA 的代码。问题在于默认行为不包含这些约束。
修复
// ❌ AI 默认生成:视觉正确,语义全缺
const Sidebar = ({ sections }) => (
<div className="w-64 bg-gray-50 p-4">
{sections.map(section => (
<div key={section.id}>
{/* 标题层级丢失,屏幕阅读器无法通过标题导航 */}
<div className="text-xs font-bold text-gray-400 uppercase">
{section.title}
</div>
{section.items.map(item => (
// div + onClick = 键盘用户的死胡同
<div
key={item.id}
className="flex items-center gap-2 p-2 hover:bg-gray-100 rounded cursor-pointer"
onClick={() => navigate(item.path)}
>
<item.icon className="w-4 h-4" />
<span>{item.label}</span>
</div>
))}
</div>
))}
</div>
)// ✅ 修复后:语义 HTML + 键盘可达 + ARIA 状态
const Sidebar = ({ sections }) => (
// nav 地标让屏幕阅读器用户可以快速跳转
<nav className="w-64 bg-gray-50 p-4" aria-label="主导航">
{sections.map(section => (
<div key={section.id} className="mb-4">
{/* 正确的标题层级,可被标题导航发现 */}
<h2 className="text-xs font-bold text-gray-400 uppercase">
{section.title}
</h2>
<ul className="mt-1 space-y-0.5" role="list">
{section.items.map(item => (
<li key={item.id}>
{/* 语义化链接,键盘自动可达 */}
<a
href={item.path}
className="flex items-center gap-2 p-2 hover:bg-gray-100 rounded"
aria-current={isActive(item.path) ? 'page' : undefined}
>
{/* aria-hidden 让装饰图标不被屏幕阅读器朗读 */}
<item.icon className="w-4 h-4" aria-hidden="true" />
<span>{item.label}</span>
</a>
</li>
))}
</ul>
</div>
))}
</nav>
)核心差异:
- 地标角色:外层
<div>改为<nav aria-label="主导航">,屏幕阅读器用户可以直接跳转到导航区域 - 标题层级:
<div>改为<h2>,让标题导航功能生效 - 交互元素:
<div onClick>改为<a href>,键盘 Tab 自动可达,无需额外tabIndex和onKeyDown - 图标处理:装饰性图标加
aria-hidden="true",避免屏幕阅读器朗读无意义的图标名称 - 当前状态:
aria-current="page"标识当前页面,比单纯的高亮背景色更可靠
微软的 A11y LLM Eval 项目数据显示,默认情况下最好的模型也只能通过 25% 的可访问性检查。但加入自定义指令(如 .cursorrules 或 .github/copilot-instructions.md 中的可访问性约束)后,通过率可以提升到 60%。如果再配合专门的可访问性技能进行第二轮审查,通过率能达到 86%[2]。
案例三:多语言文案溢出与布局崩塌
场景
AI 生成了一个 SaaS 产品的定价页面,包含三列定价卡片。每张卡片有功能列表、价格和「开始免费试用」按钮。中文环境下看起来完美。
翻车
切到德语环境后,「Kostenlose Testversion starten」把按钮撑到了 280px 宽,溢出了 240px 的卡片容器。功能列表里「包含无限次项目协作」翻译为「Unbegrenzte Zusammenarbeit an Projekten einschließen」后,每个列表项都从单行变成了两行,整张卡片的高度超出了设计基准的 2 倍,和三列布局的其他卡片高度完全不对齐。
这不是极端情况。不同语言相比英文,文本膨胀率通常在 30%–300% 之间[3]。
诊断
AI 生成 UI 时默认以英文(或 prompt 中使用的语言)为基准设计布局。按钮使用 px 固定内边距但没有 min-width,卡片没有对齐高度的机制,功能列表项没有行数限制。
修复
/* ❌ AI 默认生成:固定宽度 + 无溢出处理 */
.pricing-card {
width: 240px;
padding: 24px;
}
.cta-button {
width: 100%;
padding: 8px 16px;
/* 长文案直接溢出容器 */
}
.feature-list li {
/* 无行数限制,长翻译撑高整个列表 */
font-size: 14px;
line-height: 1.5;
}/* ✅ 修复后:弹性宽度 + 溢出防护 + 高度对齐 */
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
align-items: stretch; /* 所有卡片等高 */
gap: 24px;
}
.pricing-card {
display: flex;
flex-direction: column;
min-width: 0; /* 允许在窄屏下收缩 */
}
.cta-button {
/* min-height 保证按钮在不同文案长度下高度一致 */
min-height: 44px;
padding: 8px 16px;
/* 允许文案换行但不溢出 */
white-space: normal;
word-break: break-word;
}
.feature-list li {
font-size: 14px;
line-height: 1.5;
/* 限制最多两行,超出省略 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}关键修复点:
- 网格布局:从固定
width: 240px改为grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)),卡片自适应可用空间 - 等高卡片:
align-items: stretch保证同行卡片高度一致 - 按钮防护:
min-height: 44px保证触控区域符合 WCAG 2.5.8 标准,white-space: normal允许文案自然换行 - 列表截断:
-webkit-line-clamp: 2限制功能描述最多两行,用title属性或 tooltip 展示完整内容
| 语言 | 相比英文的典型膨胀率 | UI 设计需要预留的空间 |
|---|---|---|
| 德语 | +30% – +40% | 按钮和标签需要 1.5 倍宽度 |
| 法语 | +15% – +30% | 对话和操作类文案需要额外余量 |
| 芬兰语 | +30% – +60% | 复合词导致极端长度 |
| 日语/中文 | -10% – -30% | 文本更短,但竖排和字体大小需要注意 |
| 阿拉伯语/希伯来语 | 长度相近 | 需要 RTL 布局翻转,部分文案会膨胀 |
这张表的意义在于:如果你的 UI 只在英文或中文环境下测试过,它大概率在其他语言环境下会出问题。AI 生成 UI 时不会主动告诉你这一点。
AI 生成 UI 的审查流程
与其依赖个人经验逐项检查,不如把审查流程系统化。下面是我在实际项目中使用的审查流程:
每个阶段有明确的检查目标,不依赖记忆,可以复用到任何 AI 生成的 UI 组件。
六维审查对比
把审查涉及的六个维度做一个系统对比,帮助理解每个维度的关注点和工具链:
| 审查维度 | 核心关注点 | AI 最容易出错的地方 | 推荐工具 | 发现成本 |
|---|---|---|---|---|
| 布局稳定性 | 状态切换时的尺寸跳动 | 忽略空/加载/错误状态的布局 | Chrome DevTools Layout Shift | 低 |
| 响应式适配 | 320px–2560px 各断点表现 | 默认桌面优先,固定宽度 | Chrome Device Mode + Responsinator | 低 |
| 可访问性 | 键盘/屏幕阅读器/对比度 | 语义缺失、ARIA 全缺 | axe-core + Lighthouse | 中 |
| 文案溢出 | 多语言环境下文本是否超出容器 | 只按 prompt 语言设计 | 浏览器语言切换 + 伪翻译 | 中 |
| 真实数据 | 真实长度/数量的数据是否撑破布局 | 只用短 mock 数据 | API 联调 + 数据工厂 | 高 |
| 交互状态 | hover/focus/active/disabled 全覆盖 | 只生成静态视觉 | Storybook 状态枚举 | 中 |
审查清单(15 项)
按阶段分组,可以直接打印出来贴在显示器旁边。
阶段一:静态检查(代码合入前)
- 1. TypeScript 编译零错误 —
tsc --noEmit通过,AI 生成的类型有时会用any绕过编译 - 2. ESLint 可访问性规则全开 —
eslint-plugin-jsx-a11y的click-events-have-key-events、no-static-element-interactions、aria-props等规则设为 error - 3. 构建无报错 —
next build/vite build通过,无 SSR hydration 不匹配警告
阶段二:视觉审查(桌面端)
- 4. 真实数据替换 — 把所有 mock 数据替换为真实或接近真实的数据:长名字、长地址、长标题、空数组、null 字段
- 5. 四状态检查 — 每个组件检查空状态、加载中、正常、错误四种状态,布局不应发生跳动
- 6. 超长文本测试 — 用户名、标题、描述字段填充 50 字符以上的文本,验证截断策略是否生效
阶段三:视觉审查(移动端)
- 7. 最小宽度 320px — 在 Chrome DevTools 里切到 iPhone SE 尺寸(320px),检查所有组件是否正常
- 8. 触控目标尺寸 — 所有可点击元素的最小触控区域为 44×44px(WCAG 2.5.8 Target Size)
- 9. 横屏模式 — 检查 812px 宽度的横屏模式,特别是固定定位元素和底部导航栏
阶段四:交互与可访问性
- 10. Tab 键导航 — 从页面顶部开始按 Tab,验证所有交互元素可达、焦点顺序合理、焦点样式可见
- 11. Enter/Space 激活 — 按钮和链接在获得焦点后,Enter 和 Space 键都能触发操作
- 12. Escape 关闭 — 弹窗、下拉菜单、Toast 在按 Escape 键后应该关闭
- 13. 不只看颜色 — 错误状态、选中状态不能只用颜色区分,需要同时有图标、文字或边框变化
阶段五:多语言与国际化
- 14. 伪翻译测试 — 使用伪翻译工具(如 i18next 的 pseudo 插件)把所有文案替换为膨胀 1.5 倍的测试文本,检查溢出
- 15. RTL 布局 — 如果支持阿拉伯语或希伯来语,检查
dir="rtl"下布局是否正确翻转
防御体系:从 prompt 到 CI
审查清单是事后检查,更可靠的做法是把防御前置。下面是我在项目中实际使用的分层防御策略:
| 防御层 | 手段 | 覆盖范围 | 维护成本 | 可靠性 |
|---|---|---|---|---|
| Prompt 层 | 在 .cursorrules / copilot-instructions.md 中写入可访问性和响应式约束 | AI 生成时 | 低 | 中(模型可能忽略) |
| 组件层 | 使用 Radix UI / React Aria 等无头组件库 | 所有组件 | 中 | 高(API 级保障) |
| 代码层 | eslint-plugin-jsx-a11y + pre-commit hook | 每次提交 | 低 | 高 |
| 测试层 | jest-axe 组件测试 + @axe-core/playwright E2E | CI 阶段 | 中 | 高 |
| CI 层 | GitHub Actions 自动运行 a11y 审计,不通过则阻止合并 | 每次 PR | 中 | 最高 |
最高杠杆的两个干预措施:
-
eslint-plugin-jsx-a11y在 CI 中设为 error — 无论哪个 AI 工具生成的代码,不符合可访问性规则的 PR 无法合并。这是一次性配置、永久生效的防线。 -
架构决策使用无头组件库 — Radix UI、React Aria 这类库把可访问性编码到 API 契约里。当 AI 使用
<Dialog>组件时,aria-modal、焦点陷阱、Escape 关闭都是自动的。AI 的工作缩减为视觉组合,可访问性是结构性的。
Vercel 的 v0 是目前例外做得比较好的工具——它默认输出基于 Radix 的 shadcn/ui 组件,继承了 Radix 的可访问性契约。但这不是所有 AI UI 工具的默认行为,不能假设每个工具都有这层保障。
写在最后
AI 生成 UI 的价值是真实的——它把从想法到原型的速度提升了一个数量级。但「原型速度」和「生产质量」之间的差距,需要一套系统性的审查流程来填补。
这份清单的核心思路是:不要只看 AI 给你看的,要主动去找 AI 没告诉你的。长文本、空数据、键盘导航、多语言——这些不是边缘场景,是生产环境的日常。
审查的成本远低于修复的成本。Master.dev 的数据显示,在 prompt 阶段加入可访问性约束,每个组件增加 3–8 分钟;而事后修复同一个问题,需要 45–90 分钟[2]。投入产出比很清楚。
把这份清单当作团队的标准操作流程(SOP),每次 AI 生成 UI 后走一遍。走几次之后,它会变成你的直觉。
参考资料
<a id="ref-1"></a>[1] CodeRabbit. AI-Generated Code Has 1.7x More Bugs (2025 Data). Shiplight, 2026. 基于 470 个真实 GitHub PR 的分析,AI 生成代码问题率为人类代码的 1.7 倍。链接
<a id="ref-2"></a>[2] Fairchild, M. AI-Generated Accessibility, an Update — Frontier Models Still Fail. Dev.to, 2026. 基于 A11y LLM Eval 项目的基准测试,默认通过率 12%,配合技能审查可达 86%。链接
<a id="ref-3"></a>[3] Master.dev Blog. AI-Generated UI Is Inaccessible by Default. Master.dev, 2026. 系统分析 LLM 生成 UI 代码的 10 类可访问性失败和五层修复体系。链接
<a id="ref-4"></a>[4] Google. Cumulative Layout Shift (CLS). web.dev, 2023. CLS 的官方定义、计算方式和优化指南。链接
<a id="ref-5"></a>[5] W3C. WAI-ARIA Authoring Practices Guide. W3C, 2025. ARIA 设计模式的权威参考,定义复合小部件的键盘交互和语义规范。链接
<a id="ref-6"></a>[6] Microsoft. A11y LLM Eval — Accessibility Evaluation Harness. GitHub, 2026. 使用 axe-core 对 LLM 生成 HTML 进行可访问性基准测试的开源工具。链接
<a id="ref-7"></a>[7] Hansem Global. Solving Text Overflow Issue in UI Term Translation. Hansem Blog, 2025. UI 翻译中文本溢出和布局变形的检测方法与实践。链接