无障碍开发实践:让你的网站对所有人可用
前端开发经常忽略无障碍(Accessibility,简称 a11y)。但实际上,做无障碍不只是"道德正确",它直接影响你网站能覆盖多少用户,也和 SEO 有关系。
无障碍的核心原则
WCAG 2.1 定义了四个原则:
- 可感知:信息能被感知到(文本替代、字幕、对比度)
- 可操作:界面能被操作(键盘导航、足够的时间)
- 可理解:内容和 UI 可被理解(清晰的语言、可预测的行为)
- 健壮性:内容能被各种工具访问(语义化 HTML、ARIA)
语义化 HTML
无障碍的第一步也是最重要的一步:用正确的 HTML 标签。
<!-- 差:全用 div -->
<div class="header">
<div class="nav">
<div class="nav-item">首页</div>
<div class="nav-item">关于</div>
</div>
</div>
<!-- 好:语义化标签 -->
<header>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
语义化标签的好处:
- 屏幕阅读器能正确解析页面结构
- 键盘导航更合理
- SEO 更好(搜索引擎能理解页面结构)
常见的语义化替换:
<div class="button">→<button><div onclick="...">→<button onClick="..."><div class="img">→<img>,加上 alt 文本<div class="list">→<ul>/<ol>+<li><div class="main">→<main>
键盘可访问性
不是所有人都用鼠标。视力障碍用户、运动障碍用户、以及很多开发者习惯用键盘操作。
Tab 导航
<!-- 原生元素默认支持 Tab 导航 -->
<button>可聚焦</button>
<a href="#">可聚焦</a>
<input type="text" />
<!-- div 不可聚焦,除非加 tabindex -->
<div tabindex="0">手动添加可聚焦</div>
<!-- tabindex="-1" 可以用 JS 聚焦,但不参与 Tab 导航 -->
<div tabindex="-1" ref={ref}>...</div>
Focus 管理
模态框、抽屉这类组件打开时,焦点应该被限制在组件内部:
// 模态框打开时,焦点移到模态框内第一个可聚焦元素
function openModal() {
modalRef.current?.showModal();
const firstFocusable = modalRef.current?.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
}
// 模态框关闭时,焦点回到触发按钮
function closeModal() {
modalRef.current?.close();
triggerRef.current?.focus();
}
键盘快捷键
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closeModal();
}
if (e.key === 'ArrowDown') {
e.preventDefault();
focusNextItem();
}
if (e.key === 'ArrowUp') {
e.preventDefault();
focusPrevItem();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
颜色和对比度
WCAG AA 标准要求正常文本的对比度至少 4.5:1,大文本至少 3:1。
/* 差:灰色文字在白色背景上,对比度不够 */
.text { color: #999; background: white; }
/* 好:足够高的对比度 */
.text { color: #555; background: white; }
可以用浏览器 DevTools 检查对比度,或者用在线工具如 WebAIM Contrast Checker。
暗色模式下特别要注意对比度。很多人做的暗色主题看起来很酷,但文字看不清。
ARIA 属性
当 HTML 语义不够用时,用 ARIA 补充:
<!-- 加载状态 -->
<button aria-busy="true" aria-label="正在加载">
<span class="spinner"></span>
</button>
<!-- 展开收起 -->
<button aria-expanded="false" aria-controls="content-1">
更多信息
</button>
<div id="content-1" role="region" hidden>
详细内容...
</div>
<!-- 标签页 -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">标签1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">标签2</button>
</div>
<div role="tabpanel" id="panel-1">内容1</div>
<div role="tabpanel" id="panel-2" hidden>内容2</div>
ARIA 的原则:不要过度使用。能用原生 HTML 表达的,就不要用 ARIA。ARIA 是补充,不是替代。
图片替代文本
<!-- 有意义的图片:提供描述性的 alt -->
<img src="chart.png" alt="2025年Q1收入同比增长23%" />
<!-- 装饰性图片:空 alt -->
<img src="divider.png" alt="" role="presentation" />
<!-- 复杂图片:用 aria-describedby 关联描述 -->
<figure>
<img src="infographic.png" alt="系统架构图" aria-describedby="fig-desc" />
<figcaption id="fig-desc">系统由前端、API 网关、微服务三层组成...</figcaption>
</figure>
表单无障碍
<!-- 差:label 和 input 没有关联 -->
<input type="text" placeholder="用户名" />
<!-- 好:label 关联 input -->
<label for="username">用户名</label>
<input id="username" type="text" />
<!-- 错误提示关联到输入框 -->
<input id="email" type="email" aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" role="alert">请输入有效的邮箱地址</span>
role="alert" 让屏幕阅读器立即朗读错误信息,不用等待用户导航到错误位置。
React 中的无障碍
如果你用 React,注意以下几点:
- 使用语义化 HTML,不要什么都用 div
- 使用
aria-*属性(React 支持所有 ARIA 属性) - 用
React.Fragment而不是div做包装 - 列表渲染时提供
aria-label - 动态内容用
aria-live区域
// 搜索结果用 aria-live 区域,屏幕阅读器会自动朗读
function SearchResults({ results }) {
return (
<div aria-live="polite" aria-atomic="true">
{results.length === 0
? '没有找到结果'
: `找到 ${results.length} 条结果`}
</div>
);
}
测试工具
- axe DevTools:Chrome 插件,自动检测无障碍问题
- Lighthouse:内置无障碍审计
- WAVE:在线无障碍检查工具
- VoiceOver / NVDA:用屏幕阅读器实际体验
在 CI 里加 axe-core 做自动化检查:
import axe from '@axe-core/react';
it('should not have accessibility violations', async () => {
const { container } = render(<App />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
无障碍不是一个额外的需求,而是产品的基本质量要求。从语义化 HTML 开始,养成好习惯,大部分问题可以预防。
ReactCSS
返回首页