AI 生成 UI 的审查清单

0阅读12分钟

截图好看,不等于能用

上个月我接手了一个用 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 对所有文本容器使用了固定的 heightwhite-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 个可访问性失败:导航区域缺少 &lt;nav&gt; 地标、分组标题用 &lt;div&gt; 而非 &lt;h2&gt;、折叠按钮的 chevron 旋转只是视觉效果没有 aria-expanded、所有可点击的 &lt;div&gt; 不在 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>
)

核心差异:

  • 地标角色:外层 &lt;div&gt; 改为 &lt;nav aria-label="主导航"&gt;,屏幕阅读器用户可以直接跳转到导航区域
  • 标题层级&lt;div&gt; 改为 &lt;h2&gt;,让标题导航功能生效
  • 交互元素&lt;div onClick&gt; 改为 &lt;a href&gt;,键盘 Tab 自动可达,无需额外 tabIndexonKeyDown
  • 图标处理:装饰性图标加 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 的审查流程

与其依赖个人经验逐项检查,不如把审查流程系统化。下面是我在实际项目中使用的审查流程:

流程图画布 · 115%
Mermaid 流程图加载中...

每个阶段有明确的检查目标,不依赖记忆,可以复用到任何 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-a11yclick-events-have-key-eventsno-static-element-interactionsaria-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 E2ECI 阶段
CI 层GitHub Actions 自动运行 a11y 审计,不通过则阻止合并每次 PR最高

最高杠杆的两个干预措施:

  1. eslint-plugin-jsx-a11y 在 CI 中设为 error — 无论哪个 AI 工具生成的代码,不符合可访问性规则的 PR 无法合并。这是一次性配置、永久生效的防线。

  2. 架构决策使用无头组件库 — Radix UI、React Aria 这类库把可访问性编码到 API 契约里。当 AI 使用 &lt;Dialog&gt; 组件时,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 翻译中文本溢出和布局变形的检测方法与实践。链接

评论 0

0 / 1000