共计 2883 个字符,预计需要花费 8 分钟才能阅读完成。
为什么我们需要关注无障碍访问?
很多开发者可能没有意识到,我们日常开发的网页对部分用户来说可能是难以使用的。比如:

- 色盲用户无法区分红色错误提示和绿色成功提示
- 运动障碍用户无法使用鼠标,但网页的所有功能却只能通过鼠标操作
- 屏幕阅读器用户听到的是混乱的、没有语义结构的页面内容
根据 WHO 统计,全球有超过 10 亿人患有某种形式的残疾。忽视无障碍性意味着我们主动将这部分用户拒之门外。更现实的是,许多国家和地区已将无障碍访问作为法律要求,比如美国的 Section 508 和欧盟的 EN 301 549 标准。
理解无障碍标准 WCAG
Web 内容可访问性指南(WCAG)2.1 是目前最广泛采用的标准,分为三个级别:
- A 级(最低要求):比如为非文本内容提供文本替代
- AA 级(推荐标准):比如颜色对比度至少达到 4.5:1
- AAA 级(高要求):比如颜色对比度达到 7:1
对于大多数项目,达到 AA 级就是不错的目标。AA 级中几个关键要求包括:
- 键盘可操作:所有功能都能通过键盘访问
- 足够的对比度:文本与背景的对比度至少 4.5:1
- 明确的状态指示:比如表单错误不仅用颜色标记,还要有文字说明
WAI-ARIA 核心三要素
当 HTML 语义不足时,WAI-ARIA(Accessible Rich Internet Applications)规范提供了补充方案,重点关注三个属性:
- role(角色):定义元素的类型,如
role="button" - aria-label/labelledby(标签):提供可访问的名称
- aria-* 状态 / 属性:如
aria-expanded="true"表示展开状态
实战:构建无障碍 React 组件
下面是一个带键盘导航的菜单组件示例(TypeScript):
interface MenuItem {
id: string;
label: string;
onClick: () => void;}
interface AccessibleMenuProps {items: MenuItem[];
label: string;
}
export function AccessibleMenu({items, label}: AccessibleMenuProps) {const [isOpen, setIsOpen] = React.useState(false);
const [activeIndex, setActiveIndex] = React.useState(0);
const menuRef = React.useRef<HTMLDivElement>(null);
// 管理键盘导航
const handleKeyDown = (e: React.KeyboardEvent) => {if (!isOpen) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex((prev) => (prev + 1) % items.length);
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex((prev) => (prev - 1 + items.length) % items.length);
break;
case 'Enter':
case 'Space':
e.preventDefault();
items[activeIndex].onClick();
setIsOpen(false);
break;
case 'Escape':
setIsOpen(false);
break;
case 'Tab':
setIsOpen(false);
break;
}
};
// 确保焦点在菜单打开时正确管理
React.useEffect(() => {if (isOpen && menuRef.current) {const firstItem = menuRef.current.querySelector('[role="menuitem"]') as HTMLElement;
firstItem?.focus();}
}, [isOpen]);
return (
<div
ref={menuRef}
role="menu"
aria-label={label}
onKeyDown={handleKeyDown}
>
<button
aria-haspopup="true"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
{label}
</button>
{isOpen && (
<ul role="menu">
{items.map((item, index) => (
<li
key={item.id}
role="menuitem"
tabIndex={-1}
aria-current={activeIndex === index}
style={{
background: activeIndex === index ? '#f0f0f0' : 'transparent',
outline: 'none'
}}
onClick={() => {item.onClick();
setIsOpen(false);
}}
onMouseEnter={() => setActiveIndex(index)}
>
{item.label}
</li>
))}
</ul>
)}
</div>
);
}
这个组件实现了:
- 完整的键盘导航(上下箭头选择,Enter/Space 确认)
- 正确的 ARIA 属性标记
- 焦点管理
- 跳过链接(Skip Link)支持
自动化检测工具:axe-core
除了手动测试,我们可以使用 axe-core 进行自动化检测:
- 安装 axe-core 浏览器插件或 npm 包
- 在开发环境中运行检测:
import axe from 'axe-core';
// 在测试中运行
axe.run(document.body, {}, (err, results) => {if (err) throw err;
console.log(results.violations); // 输出可访问性问题
});
三个常见反模式
- 滥用 aria-hidden:
- 错误做法:
<div aria-hidden="true"> 重要内容 </div> -
正确做法:只对纯装饰性内容使用
-
缺失 alt 文本的装饰性图片:
- 错误做法:
<img src="decorative.jpg"> -
正确做法:
<img src="decorative.jpg" alt=""role="presentation"> -
自定义控件缺少键盘支持:
- 错误做法:只实现鼠标点击事件
- 正确做法:同时处理键盘事件(Enter/Space)
进阶测试建议
- 使用屏幕阅读器测试:
- Mac:内置 VoiceOver(Cmd+F5 激活)
-
Windows:NVDA(免费)或 JAWS(商业)
-
纯键盘测试:
-
尝试只用 Tab/Shift+Tab/ 箭头键操作整个页面
-
高对比度模式测试:
- 在 Windows 设置中启用高对比度主题
资源推荐
记住,无障碍性不是一次性的工作,而应该成为开发流程的一部分。从今天开始,试着在每次代码审查时都问一句:这部分对残障用户友好吗?
正文完
