关键要点
- 语义化 HTML 免费提供 80% 的无障碍能力——先使用原生元素,再考虑 ARIA。
- WCAG 2.2 引入了关于焦点外观、拖拽替代方案和目标尺寸的新成功标准。
- 颜色对比度至少为 4.5:1(普通文本)和 3:1(大文本),AA 级别。
- 每个交互元素必须支持键盘操作并有可见的焦点指示器。
- 表单需要关联标签、清晰的错误消息和程序化的验证公告。
- 使用自动化工具(axe、Lighthouse)和手动屏幕阅读器(NVDA、VoiceOver)进行测试。
Web 无障碍确保网站和应用程序对所有人可用,包括视觉、听觉、运动或认知障碍的用户。全球超过13亿人生活在某种形式的残障中,无障碍不仅是法律要求,也是道德义务和商业优势。本指南涵盖 WCAG 2.2 合规级别、语义化 HTML、ARIA 属性、键盘导航、屏幕阅读器优化和测试策略。
1. WCAG 2.2 指南与合规级别
WCAG 2.2 由 W3C 于 2023 年 10 月发布,是当前的 Web 无障碍标准。它在 WCAG 2.1 的基础上增加了九个新的成功标准,围绕四个原则组织:可感知、可操作、可理解和健壮。
WCAG 定义三个合规级别:A 级覆盖最低基线,AA 级是大多数组织的标准目标,AAA 级代表最高级别但不作为通用要求。
| Level | Criteria Count | Target Audience | Legal Requirement |
|---|---|---|---|
| A | 30 | Minimum baseline | Required everywhere |
| AA | 24 | Standard target | ADA, Section 508, EAA |
| AAA | 33 | Enhanced accessibility | Not generally required |
WCAG 2.2 新增:焦点不被遮挡(AA)、拖拽动作替代(AA)、最小目标尺寸 24x24 像素(AA)、一致的帮助(A)、冗余输入(A)。
2. 语义化 HTML 元素与地标
语义化 HTML 是无障碍 Web 开发的基础。原生 HTML 元素携带隐式角色、状态和键盘行为。button 元素可聚焦、可通过 Enter 和 Space 激活——div 加 onclick 不具备这些行为。
Semantic vs Non-Semantic HTML
<!-- BAD: Non-semantic (inaccessible) -->
<div class="header">
<div class="nav">
<div onclick="navigate('/home')">Home</div>
<div onclick="navigate('/about')">About</div>
</div>
</div>
<div class="main">
<div class="title">Page Title</div>
<div class="btn" onclick="submit()">Submit</div>
</div>
<!-- GOOD: Semantic (accessible) -->
<header>
<nav aria-label="Main navigation">
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<h1>Page Title</h1>
<button type="submit">Submit</button>
</main>HTML5 地标元素直接映射到 ARIA 地标角色:header 对应 banner,nav 对应 navigation,main 对应 main,aside 对应 complementary,footer 对应 contentinfo。
| HTML Element | ARIA Landmark Role | Purpose |
|---|---|---|
| <header> | banner | Site-wide header content |
| <nav> | navigation | Navigation links |
| <main> | main | Primary page content |
| <aside> | complementary | Supporting content |
| <footer> | contentinfo | Site-wide footer content |
| <section> | region (with label) | Thematic grouping |
| <form> | form (with label) | User input form |
标题层次传达文档结构。每页一个 h1,然后按顺序使用 h2 到 h6。67.5% 的屏幕阅读器用户使用标题作为主要导航方式。
3. ARIA 角色、状态和属性
ARIA 在原生 HTML 语义不足时提供角色、状态和属性。ARIA 第一规则:如果存在具有等效语义的原生 HTML 元素,就不要使用 ARIA。
ARIA Tab Interface Example
<!-- Accessible tab component with ARIA -->
<div role="tablist" aria-label="Product info">
<button role="tab"
id="tab-details"
aria-selected="true"
aria-controls="panel-details"
tabindex="0">Details</button>
<button role="tab"
id="tab-reviews"
aria-selected="false"
aria-controls="panel-reviews"
tabindex="-1">Reviews</button>
<button role="tab"
id="tab-shipping"
aria-selected="false"
aria-controls="panel-shipping"
tabindex="-1">Shipping</button>
</div>
<div role="tabpanel"
id="panel-details"
aria-labelledby="tab-details"
tabindex="0">
<p>Product details content here.</p>
</div>
<!-- Keyboard: Arrow keys between tabs,
Tab key moves into active panel -->ARIA 角色分为六类:地标角色、部件角色、文档结构角色、实时区域角色、窗口角色和抽象角色。
ARIA 状态和属性传达动态信息:aria-expanded 指示折叠区域状态,aria-live 向屏幕阅读器宣告动态内容更新。
aria-live Regions
<!-- Status message (polite) -->
<div aria-live="polite" aria-atomic="true">
3 results found for "accessibility"
</div>
<!-- Error alert (assertive) -->
<div role="alert">
<!-- role="alert" implies assertive -->
Payment failed. Check your card details.
</div>
<!-- Progress update -->
<div role="status">
<!-- role="status" implies polite -->
Uploading: 67% complete
</div>4. 键盘导航与焦点管理
所有交互元素必须仅通过键盘即可操作。使用 tabindex 0 将元素添加到自然 Tab 序列。永远不要使用大于 0 的 tabindex 值。
| Key | Action | Element Type |
|---|---|---|
| Tab | Move to next focusable element | All interactive |
| Shift + Tab | Move to previous element | All interactive |
| Enter | Activate link or button | Links, buttons |
| Space | Toggle button/checkbox | Buttons, checkboxes |
| Escape | Close dialog or dropdown | Modals, menus |
| Arrow keys | Navigate within widget | Tabs, menus, radios |
焦点管理对单页应用至关重要。内容变化时需程序化移动焦点。使用 tabindex -1 使元素可编程聚焦。模态框需要焦点陷阱。
Focus Trap for Modal Dialog
function trapFocus(dialog) {
const focusable = dialog.querySelectorAll(
'button, [href], input, select,' +
' textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
dialog.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
first.focus();
}WCAG 2.2 要求可见的焦点指示器,至少 2px 实线轮廓,3:1 对比度。考虑使用 :focus-visible 替代 :focus。
Focus Indicator CSS
/* Custom focus indicator (WCAG 2.2) */
:focus-visible {
outline: 2px solid #2563eb;
outline-offset: 2px;
border-radius: 2px;
}
/* High contrast for dark backgrounds */
.dark-section :focus-visible {
outline: 2px solid #fbbf24;
outline-offset: 2px;
}5. 屏幕阅读器优化
主要屏幕阅读器:NVDA(Windows免费)、JAWS(商业)、VoiceOver(macOS/iOS内置)、TalkBack(Android内置)。
无障碍名称计算顺序:aria-labelledby > aria-label > label > 内容 > title > alt。确保每个交互元素都有无障碍名称。
Visually Hidden (Screen Reader Only)
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Usage: visible to screen readers only */
<button>
<svg aria-hidden="true"><!-- icon --></svg>
<span class="sr-only">Close dialog</span>
</button>实时区域在不移动焦点的情况下通知屏幕阅读器。polite 等待用户完成当前操作,assertive 立即中断。
6. 颜色对比度与视觉无障碍
AA 级要求普通文本 4.5:1、大文本 3:1 对比度。AAA 级提高到 7:1 和 4.5:1。非文本元素至少需要 3:1。
| WCAG Level | Normal Text | Large Text | UI Components |
|---|---|---|---|
| AA (required) | 4.5:1 | 3:1 | 3:1 |
| AAA (enhanced) | 7:1 | 4.5:1 | N/A |
不要仅依靠颜色传达信息。链接需要额外的视觉指示器。表单错误应结合颜色、图标和文本。
使用 CSS 媒体查询支持用户偏好:prefers-reduced-motion、prefers-contrast、prefers-color-scheme。
Respecting User Preferences
/* Reduce or disable animations */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Increase contrast when requested */
@media (prefers-contrast: more) {
:root {
--text-color: #000000;
--bg-color: #ffffff;
--border-color: #000000;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--text-color: #f1f5f9;
--bg-color: #0f172a;
}
}7. 表单无障碍
每个表单输入必须有程序化关联的标签。占位符文本不能替代标签。
Accessible Form Example
<form novalidate>
<fieldset>
<legend>Contact Information</legend>
<label for="email">Email address</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-hint email-error"
aria-invalid="false"
/>
<span id="email-hint">
We will never share your email.
</span>
<span id="email-error" role="alert"></span>
</fieldset>
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact"
value="email" /> Email
</label>
<label>
<input type="radio" name="contact"
value="phone" /> Phone
</label>
</fieldset>
<button type="submit">Send message</button>
</form>使用 fieldset 和 legend 对相关字段分组。使用 aria-describedby 链接说明文本。
无障碍错误处理:识别错误字段、描述错误、帮助用户修复。使用 aria-invalid 和 aria-live 通告错误。
Error Handling Pattern
function handleSubmit(form) {
const errors = validate(form);
if (errors.length === 0) return true;
// Clear previous errors
form.querySelectorAll('[aria-invalid]').forEach(
el => el.setAttribute('aria-invalid', 'false')
);
// Mark fields with errors
errors.forEach(({ fieldId, message }) => {
const field = document.getElementById(fieldId);
const errorEl = document.getElementById(
fieldId + '-error'
);
field.setAttribute('aria-invalid', 'true');
errorEl.textContent = message;
});
// Focus first error field
document.getElementById(
errors[0].fieldId
).focus();
return false;
}8. 图片 Alt 文本最佳实践
所有图片必须有 alt 属性。信息性图片需要描述性文本,装饰性图片使用空 alt。
描述内容和功能而非外观。保持简洁(通常不超过125个字符)。
| Image Type | Alt Text Approach | Example |
|---|---|---|
| Informative | Describe content/function | alt="Bar chart: 40% Q4 revenue increase" |
| Decorative | Empty alt attribute | alt="" |
| Linked image | Describe destination | alt="Company Name - Homepage" |
| Icon button | Describe action | alt="Search" (not "Magnifying glass") |
| Complex chart | Brief summary + long desc | alt="Q4 sales" + aria-describedby |
| Text in image | Reproduce the text | alt="50% OFF Summer Sale" |
图标按钮描述操作而非图标:搜索按钮的 alt 应为"搜索"而非"放大镜"。
<!-- SVG icon with accessibility -->
<button>
<svg role="img" aria-label="Delete item"
viewBox="0 0 24 24">
<title>Delete item</title>
<path d="M6 19c0 1.1.9 2 2 2h8..." />
</svg>
</button>
<!-- Decorative SVG (hidden from AT) -->
<svg aria-hidden="true" focusable="false">
<use href="#decorative-divider" />
</svg>9. 响应式与移动端无障碍
WCAG 2.2 要求内容在 320px 宽度下无需水平滚动。文本可放大至 200%。不要禁用用户缩放。
触摸目标至少 44x44 像素,最低 24x24。复杂手势需提供简单替代方案。
<!-- BAD: Prevents user from zooming -->
<meta name="viewport"
content="width=device-width,
initial-scale=1, maximum-scale=1,
user-scalable=no" />
<!-- GOOD: Allows user zoom -->
<meta name="viewport"
content="width=device-width,
initial-scale=1" />
/* Touch target sizing */
.touch-target {
min-width: 44px;
min-height: 44px;
padding: 12px;
}
/* Ensure text reflows at 320px */
.content {
max-width: 100%;
overflow-wrap: break-word;
}移动屏幕阅读器使用触摸手势。VoiceOver 用户左右滑动导航、双击激活。确保自定义组件支持这些手势。
10. 无障碍表格与数据可视化
数据表格需要正确的表头标记。使用 th 加 scope 属性。为表格添加 caption 或 aria-label。
Accessible Data Table
<table>
<caption>
Quarterly Revenue (USD millions)
</caption>
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Revenue</th>
<th scope="col">Growth</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Q1 2025</th>
<td>12.4</td>
<td>+8%</td>
</tr>
<tr>
<th scope="row">Q2 2025</th>
<td>14.1</td>
<td>+13.7%</td>
</tr>
</tbody>
</table>不要用表格布局。图表和可视化需提供文本替代。
交互图表需要键盘导航和无障碍工具提示。考虑使用 aria-live 在实时仪表板中通告数据变化。
11. 使用 axe、Lighthouse 和屏幕阅读器测试
自动化测试发现 30-50% 的问题。使用 axe-core 作为主要工具。Lighthouse 提供快速概览。
Automated Testing with axe-core
// axe-core in Cypress E2E tests
describe('Accessibility', () => {
it('has no a11y violations', () => {
cy.visit('/');
cy.injectAxe();
cy.checkA11y(null, {
runOnly: {
type: 'tag',
values: ['wcag2a', 'wcag2aa',
'wcag22aa']
}
});
});
});
// axe-core in Jest unit tests
import { axe, toHaveNoViolations }
from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form is accessible', async () => {
const { container } = render(<LoginForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});手动测试:仅用 Tab、Enter、Space、Escape 和方向键。至少使用一个屏幕阅读器测试。
在 CI 管道中运行 axe 检查。在用户故事中包含无障碍验收标准。定期与使用辅助技术的真实用户一起审计。
| Tool | Type | Platform | Best For |
|---|---|---|---|
| axe DevTools | Extension | Chrome/Firefox | Quick page audits |
| axe-core | CI/CD lib | Node.js | Regression testing |
| Lighthouse | Built-in | Chrome DevTools | Quick audit scores |
| NVDA | Screen reader | Windows (free) | Manual SR testing |
| VoiceOver | Screen reader | macOS/iOS | Mac/mobile testing |
| pa11y | CLI tool | Node.js | CI pipeline |
12. 常见无障碍反模式
用 div/span 做交互元素是最常见的反模式。解决方案:使用原生 button 和 a 元素。
Common Mistakes & Fixes
<!-- Anti-pattern: clickable div -->
<div class="btn" onclick="save()">
Save
</div>
<!-- Fix: use native button -->
<button type="button" onclick="save()">
Save
</button>
<!-- Anti-pattern: missing label -->
<input type="text" placeholder="Search..." />
<!-- Fix: add associated label -->
<label for="search">Search</label>
<input type="text" id="search"
placeholder="Search..." />
<!-- Anti-pattern: redundant ARIA -->
<button role="button"
aria-label="Submit button">
Submit
</button>
<!-- Fix: remove unnecessary ARIA -->
<button type="submit">Submit</button>移除焦点轮廓而不提供替代;自动播放有声媒体;使用大于 0 的 tabindex;用 display:none 隐藏屏幕阅读器需要的内容。
ARIA 过度使用:为每个元素添加 aria-label、在 button 上添加冗余的 role=button、对可见元素使用 aria-hidden=true。
13. 法律要求(ADA、508条款、EAA)
美国 ADA 第三条适用于网站。508 条款要求联邦机构遵循 WCAG 2.0 AA。2024年DOJ规则要求州和地方政府遵循 WCAG 2.1 AA。
欧洲无障碍法案(EAA)2025年6月生效,要求网站和移动应用无障碍。EN 301 549 对应 WCAG 2.1 AA。
| Regulation | Region | Standard | Effective |
|---|---|---|---|
| ADA Title III | United States | WCAG 2.1 AA | Active |
| Section 508 | US Federal | WCAG 2.0 AA | Active |
| EAA | European Union | WCAG 2.1 AA | June 2025 |
| AODA | Ontario, Canada | WCAG 2.0 AA | Active |
| DDA | Australia | WCAG 2.0 AA | Active |
2023年美国提起超过4000起数字无障碍诉讼。主动合规远比被动补救便宜。WCAG 2.1 AA 是全球合规的实际目标。
总结
Web 无障碍是持续的实践,而非一次性清单。从语义化 HTML 开始,仅在需要时添加 ARIA,确保键盘和屏幕阅读器兼容性,保持颜色对比标准,并定期测试。WCAG 2.2 AA 应是最低目标。无障碍网站不仅合法合规,而且更快、更易用、覆盖更广泛的受众。
常见问题
WCAG 2.1 和 2.2 有什么区别?
WCAG 2.2 增加了九个新成功标准,包括焦点不被遮挡、拖拽动作替代和最小目标尺寸。满足 2.2 即满足 2.1 和 2.0。
ARIA 比语义化 HTML 更好吗?
不。语义化 HTML 始终是首选。ARIA 在原生元素不足时作为补充。第一规则是如果存在等效的原生元素就不要使用 ARIA。
最低颜色对比度要求是多少?
AA 级要求普通文本 4.5:1、大文本 3:1。AAA 级要求 7:1 和 4.5:1。非文本 UI 组件至少 3:1。
我需要用屏幕阅读器测试吗?
需要。自动化工具仅发现 30-50% 的问题。使用 VoiceOver(macOS)或 NVDA(Windows)进行手动测试。
可以用 tabindex 控制 Tab 顺序吗?
使用 tabindex=0 和 tabindex=-1。永远不要使用正值 tabindex,通过调整 DOM 顺序来修复 Tab 顺序。
如何让单页应用无障碍?
在路由变化时管理焦点,使用 aria-live 通告路由变化,确保浏览器返回按钮工作,每次路由更新文档标题。
网站不无障碍有什么法律后果?
在美国,ADA 诉讼可能导致数万美元赔偿加律师费和强制补救。在欧洲,EAA 可处以罚款。不可访问的网站会损失 15-20% 的潜在客户。
如何处理动态内容和 AJAX 更新的无障碍?
使用 aria-live 区域通告动态内容变化。非紧急使用 polite,紧急警报使用 assertive。在内容变化前添加 aria-live 属性。