AI产品的可访问性设计

引言

可访问性(Accessibility,缩写 a11y)不是"锦上添花",而是产品的基本品质。全球约 16% 的人口有某种形式的残障,包括视觉、听觉、运动和认知障碍。AI 产品由于高度依赖文本交互、实时流式输出和多模态内容,面临着独特的可访问性挑战。本文从 WCAG 标准出发,深入 AI 产品的可访问性设计实践。

一、可访问性标准与法规

1.1 WCAG 2.1 四大原则

POUR 原则:

P - Perceivable(可感知)
  用户必须能够感知到呈现的信息
  - 为图片提供替代文本
  - 为视频提供字幕
  - 内容不依赖颜色传递信息
  - 文本与背景有足够对比度

O - Operable(可操作)
  用户必须能够操作界面组件
  - 所有功能可通过键盘访问
  - 给用户足够的操作时间
  - 不使用可能引起癫痫的闪烁内容
  - 提供清晰的导航机制

U - Understandable(可理解)
  用户必须能够理解信息和操作
  - 文本可读且可理解
  - 页面行为可预测
  - 帮助用户避免和纠正错误

R - Robust(健壮)
  内容必须能被各种辅助技术解析
  - 使用语义化 HTML
  - ARIA 属性正确使用
  - 兼容主流辅助技术

1.2 合规等级

WCAG 合规等级:

Level A(最低):
  - 所有非文本内容有替代文本
  - 视频有字幕
  - 内容不依赖颜色
  - 键盘可访问

Level AA(推荐目标):
  - 对比度 >= 4.5:1(正常文字)
  - 文本可放大到 200% 不丢失内容
  - 多种导航方式
  - 错误建议

Level AAA(最高):
  - 对比度 >= 7:1
  - 手语翻译
  - 所有缩写有解释
  - 阅读水平不超过初中

法规要求:
  美国 ADA / Section 508:AA 级
  欧盟 EAA:AA 级(2025 年生效)
  中国 GB/T 37668:参考 AA 级

二、AI 产品的可访问性挑战

2.1 独特挑战清单

挑战 1: 流式输出
  问题:AI 逐字输出时,屏幕阅读器如何播报?
  影响:视障用户无法跟踪正在生成的内容

挑战 2: 动态内容更新
  问题:AI 回复插入到对话流中,焦点管理混乱
  影响:屏幕阅读器用户不知道新内容在哪里

挑战 3: 多模态输出
  问题:AI 生成的图片/图表/代码块缺乏替代描述
  影响:视障用户无法理解非文本内容

挑战 4: 复杂交互模式
  问题:拖拽排序、多步操作、上下文菜单
  影响:运动障碍用户难以精确操作

挑战 5: 信息密度
  问题:AI 仪表盘信息量大、更新频繁
  影响:认知障碍用户信息过载

挑战 6: 时间敏感
  问题:AI 实时建议/自动完成有时间限制
  影响:操作速度较慢的用户无法及时响应

2.2 辅助技术生态

视觉辅助:
  屏幕阅读器:VoiceOver (macOS/iOS), NVDA (Windows), TalkBack (Android)
  放大工具:ZoomText, macOS 放大镜
  高对比度模式:OS 内置 + 浏览器插件

运动辅助:
  键盘导航:Tab / Shift+Tab / Enter / Space / Arrow Keys
  语音控制:Voice Control (macOS), Voice Access (Android)
  开关控制:Switch Access (iOS/Android)
  眼动追踪:Tobii Eye Tracker

听觉辅助:
  字幕:浏览器实时字幕
  视觉提示:用图标/动画替代声音提示

认知辅助:
  简化模式:减少信息密度
  一致性:可预测的交互模式
  错误预防:确认对话框

三、核心实现指南

3.1 语义化 HTML

<!-- 正确:语义化结构 -->
<main>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/chat" aria-current="page">Chat</a></li>
      <li><a href="/history">History</a></li>
      <li><a href="/settings">Settings</a></li>
    </ul>
  </nav>

  <section aria-label="Chat conversation">
    <h1 class="sr-only">AI Chat</h1>

    <div role="log" aria-live="polite" aria-label="Chat messages">
      <article aria-label="User message">
        <p>How do I implement authentication?</p>
      </article>

      <article aria-label="AI response">
        <p>Here are the steps to implement authentication...</p>
      </article>
    </div>

    <form aria-label="Send message">
      <label for="chat-input" class="sr-only">Type your message</label>
      <textarea
        id="chat-input"
        placeholder="Ask anything..."
        aria-describedby="input-hint"
      ></textarea>
      <p id="input-hint" class="sr-only">
        Press Enter to send, Shift+Enter for new line
      </p>
      <button type="submit" aria-label="Send message">
        Send
      </button>
    </form>
  </section>
</main>

<!-- 错误:非语义化 -->
<div class="nav">
  <div class="link" onclick="goto('/chat')">Chat</div>
</div>
<div class="chat">
  <div class="msg">How do I...</div>
</div>

3.2 ARIA 属性最佳实践

<!-- AI 流式输出的 ARIA Live Region -->
<div
  role="log"
  aria-live="polite"
  aria-atomic="false"
  aria-relevant="additions"
>
  <!-- 新消息会被屏幕阅读器自动播报 -->
</div>

<!-- AI 思考状态 -->
<div role="status" aria-live="polite">
  <span class="sr-only">AI is thinking...</span>
  <div class="thinking-animation" aria-hidden="true">...</div>
</div>

<!-- AI 生成完成通知 -->
<div role="alert" aria-live="assertive">
  AI response complete. 350 words generated.
</div>

<!-- 可折叠的长回复 -->
<div>
  <button
    aria-expanded="false"
    aria-controls="full-response"
  >
    Show full response (1,200 words)
  </button>
  <div id="full-response" hidden>
    <!-- 完整内容 -->
  </div>
</div>

<!-- 代码块 -->
<div role="region" aria-label="Code example in Python">
  <div class="code-header">
    <span>Python</span>
    <button aria-label="Copy code to clipboard">Copy</button>
  </div>
  <pre><code>def hello():
    return "Hello World"</code></pre>
</div>

<!-- AI 置信度指示 -->
<span
  role="img"
  aria-label="High confidence"
  title="High confidence"
  class="confidence-indicator confidence-high"
></span>

3.3 键盘导航

AI 对话界面的键盘导航设计:

Tab 顺序:
1. 侧边栏导航(新建对话 → 历史列表)
2. 对话消息区域
3. 输入框
4. 发送按钮
5. 附加功能按钮(附件/语音/设置)

快捷键:
  / → 聚焦搜索框
  N → 新建对话
  Ctrl+Enter → 发送消息
  Escape → 关闭弹出层/取消编辑
  Alt+Up/Down → 在对话历史中导航
  Ctrl+C(在代码块内) → 复制代码

焦点管理:
  新消息到达时:焦点不移动(避免打断输入)
  AI 回复完成时:焦点可选移动到回复区域
  模态框打开时:焦点陷入模态框内
  模态框关闭时:焦点回到触发元素

3.4 颜色与对比度

/* 不依赖颜色传递信息 */

/* 错误:仅用颜色区分状态 */
.status-success { color: green; }
.status-error { color: red; }

/* 正确:颜色 + 图标 + 文字 */
.status-success {
  color: #059669;
}
.status-success::before {
  content: "[OK] ";  /* 或使用图标 */
}

.status-error {
  color: #DC2626;
}
.status-error::before {
  content: "[ERROR] ";
}

/* 高对比度模式支持 */
@media (prefers-contrast: high) {
  :root {
    --color-text: #000000;
    --color-bg: #FFFFFF;
    --color-border: #000000;
    --color-primary: #0000CC;
  }
}

/* 减少动效模式 */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  .typing-indicator {
    /* 用静态文字替代动画 */
    animation: none;
  }
  .typing-indicator::after {
    content: "AI is typing...";
  }
}

四、AI 特定场景的可访问性方案

4.1 流式输出

// 方案:使用 aria-live="polite" + 分段播报

class AccessibleStreamRenderer {
  constructor(containerEl, liveRegionEl) {
    this.container = containerEl;
    this.liveRegion = liveRegionEl;
    this.buffer = '';
    this.sentenceEndRegex = /[.!?。!?]\s/;
  }

  appendToken(token) {
    // 视觉更新:实时显示每个 token
    this.container.textContent += token;
    this.buffer += token;

    // 屏幕阅读器更新:按句子播报(非逐字)
    if (this.sentenceEndRegex.test(this.buffer)) {
      this.liveRegion.textContent = this.buffer.trim();
      this.buffer = '';
    }
  }

  complete() {
    // 播报剩余内容
    if (this.buffer.trim()) {
      this.liveRegion.textContent = this.buffer.trim();
      this.buffer = '';
    }
    // 通知完成
    this.liveRegion.textContent = 'AI response complete.';
  }
}

4.2 AI 生成图片

<!-- AI 生成的图片需要提供替代描述 -->

<!-- 方案 1:AI 自动生成 alt text -->
<img
  src="/generated/image-001.png"
  alt="A serene mountain landscape with snow-capped peaks,
       a calm lake in the foreground reflecting the mountains,
       surrounded by pine trees under a clear blue sky"
  role="img"
/>

<!-- 方案 2:提供图片描述按钮 -->
<figure>
  <img src="/generated/image-001.png" alt="AI generated image" />
  <figcaption>
    <button aria-expanded="false" aria-controls="img-desc-001">
      View image description
    </button>
    <div id="img-desc-001" hidden>
      A serene mountain landscape...
    </div>
  </figcaption>
</figure>

<!-- 方案 3:图表的可访问性 -->
<figure role="img" aria-label="Revenue trend chart, Q1 to Q4 2025">
  <div class="chart-visual" aria-hidden="true">
    <!-- 图表 SVG -->
  </div>
  <table class="sr-only">
    <caption>Revenue by Quarter (2025)</caption>
    <thead>
      <tr><th>Quarter</th><th>Revenue</th></tr>
    </thead>
    <tbody>
      <tr><td>Q1</td><td>$2.1M</td></tr>
      <tr><td>Q2</td><td>$3.5M</td></tr>
      <tr><td>Q3</td><td>$4.8M</td></tr>
      <tr><td>Q4</td><td>$6.2M</td></tr>
    </tbody>
  </table>
</figure>

4.3 表单与错误处理

<!-- AI 产品中的可访问表单 -->
<form aria-label="AI Model Configuration">
  <div class="form-group">
    <label for="model-select">Select AI Model</label>
    <select id="model-select" aria-describedby="model-help">
      <option value="fast">Fast (GPT-4o mini)</option>
      <option value="balanced">Balanced (GPT-4o)</option>
      <option value="premium">Premium (Claude Opus)</option>
    </select>
    <p id="model-help" class="help-text">
      Premium models provide higher quality but take longer.
    </p>
  </div>

  <div class="form-group" aria-invalid="true">
    <label for="api-key">API Key</label>
    <input
      id="api-key"
      type="password"
      aria-describedby="api-key-error"
      aria-required="true"
    />
    <p id="api-key-error" class="error-text" role="alert">
      API key is required. Get one from Settings.
    </p>
  </div>

  <div class="form-group">
    <label for="temperature">Temperature</label>
    <input
      id="temperature"
      type="range"
      min="0"
      max="2"
      step="0.1"
      value="0.7"
      aria-valuemin="0"
      aria-valuemax="2"
      aria-valuenow="0.7"
      aria-valuetext="0.7 - Balanced creativity"
    />
  </div>
</form>

五、测试与验证

5.1 自动化测试

// axe-core 自动化可访问性测试
const { AxeBuilder } = require('@axe-core/playwright');

test('Chat page accessibility', async ({ page }) => {
  await page.goto('/chat');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze();

  // 零违规
  expect(results.violations).toEqual([]);
});

// 键盘导航测试
test('Keyboard navigation works', async ({ page }) => {
  await page.goto('/chat');

  // Tab 到输入框
  await page.keyboard.press('Tab');
  await page.keyboard.press('Tab');
  await page.keyboard.press('Tab');

  const focused = await page.evaluate(() => document.activeElement.id);
  expect(focused).toBe('chat-input');

  // 输入并发送
  await page.keyboard.type('Hello AI');
  await page.keyboard.press('Enter');

  // 验证消息发送成功
  const messages = await page.locator('[role="log"] article').count();
  expect(messages).toBeGreaterThan(0);
});

5.2 手动测试清单

屏幕阅读器测试(VoiceOver / NVDA):
- [ ] 页面标题被正确读出
- [ ] 导航链接可遍历
- [ ] 对话消息按顺序读出
- [ ] AI 回复完成时有通知
- [ ] 代码块可被识别为代码区域
- [ ] 表格有正确的行列标头
- [ ] 错误提示被即时播报
- [ ] 模态框打开/关闭焦点正确

键盘测试:
- [ ] 所有功能可通过键盘操作
- [ ] 焦点顺序符合逻辑
- [ ] 焦点可见(有 outline 指示)
- [ ] 无键盘陷阱(焦点不会卡死)
- [ ] 快捷键不与浏览器/系统冲突

视觉测试:
- [ ] 文字放大 200% 无内容裁切
- [ ] 高对比度模式正常显示
- [ ] 信息不仅靠颜色传递
- [ ] 动画可通过系统设置禁用

认知测试:
- [ ] 错误提示说明如何修复
- [ ] 操作步骤清晰(不超过 3 步)
- [ ] 关键术语有解释或提示
- [ ] 超时操作有警告和延长选项

六、常用 CSS 工具类

/* 仅屏幕阅读器可见 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

/* 焦点可见样式 */
:focus-visible {
  outline: 2px solid #2563EB;
  outline-offset: 2px;
  border-radius: 2px;
}

/* 跳转链接(Skip to content) */
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  z-index: 100;
  padding: 8px 16px;
  background: #2563EB;
  color: #FFFFFF;
}
.skip-link:focus {
  top: 0;
}

/* 减少动效偏好 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

总结

AI 产品的可访问性设计核心在于三点:(1)语义化结构(HTML + ARIA)让辅助技术能正确解析界面,(2)键盘导航和焦点管理让非鼠标用户能流畅操作,(3)AI 特定场景(流式输出/动态内容/多模态)需要专门的 ARIA Live Region 和替代描述方案。技术实现推荐 axe-core 做自动化扫描覆盖 A/AA 级基础规则,再用屏幕阅读器(VoiceOver/NVDA)做手动走查验证真实体验。可访问性不是事后补救,而是从设计阶段就内置的质量属性。


Maurice | maurice_wen@proton.me