无障碍网页设计 accessibility-patterns

这是一个关于无障碍网页设计的技能,用于构建包容性的网页体验,遵循WCAG指南。关键内容包括语义HTML、ARIA属性、键盘导航、颜色对比和测试策略。关键词:无障碍、网页设计、WCAG、语义HTML、ARIA、前端开发、SEO优化。

前端开发 0 次安装 0 次浏览 更新于 3/7/2026

name: 无障碍模式 description: 遵循WCAG指南构建包容性网页体验。覆盖语义HTML、ARIA、键盘导航、颜色对比和测试策略。在无障碍、a11y、WCAG、屏幕阅读器或包容性设计请求时触发。 license: MIT

无障碍模式

从一开始就为所有人构建。

核心原则 (POUR)

原则 含义 示例
可感知 用户可以感知内容 替代文本、字幕、对比度
可操作 用户可以交互 键盘访问、足够时间
可理解 用户可以理解 清晰语言、可预测
健壮 与辅助技术兼容 有效的HTML、ARIA

WCAG 级别

级别 描述 目标
A 最低要求 必须拥有
AA 标准 行业标准、法律要求
AAA 增强 最好有

目标级别 AA 适用于大多数项目。


语义HTML

使用正确的元素

替代使用 使用
<div onclick> <button>
<span class="link"> <a href>
<div class="header"> <header>
<div class="nav"> <nav>
<div class="main"> <main>
<b> 用于强调 <strong>
<i> 用于强调 <em>

文档结构

<!DOCTYPE html>
<html lang="en">
<head>
  <title>描述性页面标题</title>
</head>
<body>
  <a href="#main" class="skip-link">跳过主要内容</a>
  
  <header>
    <nav aria-label="主导航">
      <!-- 导航 -->
    </nav>
  </header>
  
  <main id="main">
    <h1>页面标题</h1>
    <!-- 每个页面只有一个 h1 -->
    
    <article>
      <h2>章节</h2>
      <h3>子章节</h3>
    </article>
  </main>
  
  <aside aria-label="相关内容">
    <!-- 侧边栏 -->
  </aside>
  
  <footer>
    <!-- 页脚内容 -->
  </footer>
</body>
</html>

标题层次结构

h1 - 页面标题(每个页面一个)
  h2 - 主要章节
    h3 - 子章节
      h4 - 子子章节

切勿跳过级别(如 h1 → h3)

图像与媒体

替代文本

<!-- 信息性图像 -->
<img src="chart.png" alt="条形图显示第四季度销售额增长40%">

<!-- 装饰性图像 -->
<img src="decorative-border.png" alt="" role="presentation">

<!-- 复杂图像 -->
<figure>
  <img src="complex-diagram.png" alt="系统架构图">
  <figcaption>
    系统架构的详细描述...
  </figcaption>
</figure>

<!-- 图像作为链接 -->
<a href="/products">
  <img src="product.jpg" alt="查看我们的产品">
</a>

替代文本指南

图像类型 替代文本策略
信息性 描述内容和功能
装饰性 空 alt=“”
功能性 描述操作
复杂 简短alt + 较长描述
图像中的文本 包含所有文本

视频与音频

<!-- 带字幕的视频 -->
<video controls>
  <source src="video.mp4" type="video/mp4">
  <track kind="captions" src="captions.vtt" srclang="en" label="英语">
  <track kind="descriptions" src="descriptions.vtt" srclang="en" label="音频描述">
</video>

<!-- 带转录的音频 -->
<audio controls>
  <source src="podcast.mp3" type="audio/mpeg">
</audio>
<a href="transcript.html">阅读转录</a>

表单

标签

<!-- 显式标签(首选) -->
<label for="email">电子邮件地址</label>
<input type="email" id="email" name="email">

<!-- 隐式标签 -->
<label>
  电子邮件地址
  <input type="email" name="email">
</label>

<!-- 必填字段 -->
<label for="name">
  姓名 <span aria-hidden="true">*</span>
  <span class="sr-only">(必填)</span>
</label>
<input type="text" id="name" required aria-required="true">

错误处理

<div role="alert" aria-live="polite">
  <p>请修复以下错误:</p>
  <ul>
    <li><a href="#email">电子邮件是必填项</a></li>
  </ul>
</div>

<label for="email">电子邮件</label>
<input 
  type="email" 
  id="email" 
  aria-invalid="true"
  aria-describedby="email-error"
>
<span id="email-error" class="error">请输入有效的电子邮件地址</span>

表单分组

<fieldset>
  <legend>邮寄地址</legend>
  
  <label for="street">街道</label>
  <input type="text" id="street">
  
  <label for="city">城市</label>
  <input type="text" id="city">
</fieldset>

键盘导航

焦点管理

/* 永远不要移除焦点轮廓而不提供替代 */
:focus {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* 自定义焦点样式 */
:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

/* 对鼠标用户隐藏轮廓 */
:focus:not(:focus-visible) {
  outline: none;
}

Tab 键顺序

<!-- 自然Tab键顺序遵循DOM顺序 -->
<!-- 仅在必要时使用 tabindex -->

<button>第一个</button>
<button>第二个</button>
<button>第三个</button>

<!-- tabindex="0" - 添加到Tab键顺序 -->
<div tabindex="0" role="button">自定义交互元素</div>

<!-- tabindex="-1" - 可通过JS聚焦,但不通过Tab键 -->
<div tabindex="-1" id="modal">模态内容</div>

<!-- 永远不要使用 tabindex > 0 -->

跳过链接

<a href="#main" class="skip-link">跳过主要内容</a>

<style>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}
</style>

键盘模式

组件 按键
按钮 Enter, 空格
链接 Enter
菜单 方向键, Enter, Escape
选项卡 方向键, Tab
模态 Tab(捕获), Escape 关闭

ARIA

何时使用ARIA

  1. 首先,使用语义HTML
  2. 然后,如果需要,添加ARIA
  3. “没有ARIA比坏ARIA更好”

常见ARIA模式

<!-- 实时区域(用于动态内容) -->
<div aria-live="polite">内容更新将宣布</div>
<div aria-live="assertive">紧急更新中断</div>

<!-- 展开/折叠 -->
<button aria-expanded="false" aria-controls="menu">菜单</button>
<ul id="menu" hidden>...</ul>

<!-- 当前页面 -->
<nav>
  <a href="/" aria-current="page">首页</a>
  <a href="/about">关于</a>
</nav>

<!-- 忙碌状态 -->
<div aria-busy="true">加载中...</div>

<!-- 对辅助技术隐藏 -->
<span aria-hidden="true">👍</span>

<!-- 标签 -->
<button aria-label="关闭">×</button>
<nav aria-label="主导航">...</nav>
<section aria-labelledby="section-heading">
  <h2 id="section-heading">章节标题</h2>
</section>

ARIA 角色

<!-- 地标 -->
<div role="banner">页眉</div>
<div role="navigation">导航</div>
<div role="main">主体</div>
<div role="complementary">侧边栏</div>
<div role="contentinfo">页脚</div>

<!-- 小部件 -->
<div role="button">自定义按钮</div>
<div role="dialog" aria-modal="true">模态</div>
<div role="tablist">选项卡</div>
<div role="alert">错误消息</div>

颜色与对比度

对比度要求

文本大小 级别 AA 级别 AAA
普通文本 (<18px) 4.5:1 7:1
大文本 (≥18px 粗体, ≥24px) 3:1 4.5:1
UI 组件 3:1 -

颜色独立性

<!-- 不要仅依赖颜色 -->

<!-- 坏例子 -->
<span style="color: red">错误</span>

<!-- 好例子 -->
<span style="color: red">
  ⚠️ 错误: <span class="error-text">请输入有效的电子邮件</span>
</span>

测试工具

  • WebAIM 对比度检查器
  • Chrome DevTools 颜色选择器
  • Stark(Figma 插件)
  • axe DevTools

测试

自动化测试

// 使用 jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

it('应该没有无障碍违规', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

手动测试清单

  • [ ] 仅用键盘导航整个页面
  • [ ] 用屏幕阅读器测试(VoiceOver, NVDA)
  • [ ] 检查颜色对比度
  • [ ] 放大到200% - 仍然可用吗?
  • [ ] 禁用CSS - 仍然可理解吗?
  • [ ] 检查所有图像是否有替代文本
  • [ ] 验证表单标签和错误
  • [ ] 测试焦点可见性
  • [ ] 检查标题结构

屏幕阅读器测试

操作系统 屏幕阅读器 浏览器
macOS VoiceOver Safari
Windows NVDA Firefox
Windows JAWS Chrome
移动端 TalkBack Chrome
iOS VoiceOver Safari

常见模式

模态对话框

<div 
  role="dialog" 
  aria-modal="true" 
  aria-labelledby="dialog-title"
  aria-describedby="dialog-desc"
>
  <h2 id="dialog-title">确认操作</h2>
  <p id="dialog-desc">您确定要继续吗?</p>
  
  <button>取消</button>
  <button>确认</button>
</div>

焦点管理:

  1. 打开时移动焦点到模态
  2. 将焦点捕获在模态内
  3. 关闭时返回焦点到触发器

选项卡

<div role="tablist" aria-label="设置">
  <button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
    常规
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
    安全
  </button>
</div>

<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
  常规设置内容
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
  安全设置内容
</div>

参考资料

  • references/wcag-checklist.md - 完整的 WCAG 2.1 清单
  • references/aria-patterns.md - 常见 ARIA 模式
  • references/testing-tools.md - 测试工具设置