可访问性(Accessibility,常缩写为a11y)不是组件库上线前临时打补丁就能补上的能力,而是需要在架构设计阶段就融入的底层属性。一个真正可访问性友好的组件库,能让视觉障碍用户通过屏幕阅读器顺利操作,让键盘用户不依赖鼠标完成所有交互,让色觉障碍用户也能准确识别状态。本文将从语义化基础、ARIA规范、焦点管理与键盘交互、以及测试验证四个维度,系统地讲讲如何设计这样的组件库。

一、打好语义化HTML地基:原生标签优先于ARIA
组件库设计的第一原则是:能用原生HTML元素解决的,绝不自己造轮子。WAI-ARIA规范的第一条规则就明确指出,不要用ARIA去复刻原本就存在的语义。原生元素不仅自带语义,还免费附带了键盘行为、焦点行为和平台级无障碍支持,这些如果用div自己实现,工作量巨大且极易遗漏。
举个例子,设计一个按钮组件时,永远不要用<div role="button">代替<button>标签。原生button元素天然可聚焦、可通过Enter和Space触发、会被屏幕阅读器正确播报为按钮,而div版本则需要手动处理tabindex、keydown事件,还经常忘记阻止滚动等默认行为。一个常见的错误实现如下:
<!-- 错误做法:用div模拟按钮 --> <div class="btn" onclick="submit()">提交</div> <!-- 正确做法:使用原生button --> <button type="button" class="btn" onclick="submit()">提交</button>
类似的,导航组件应使用<nav>,列表使用<ul>/<li>,表单控件使用<input>、<select>等。组件库设计时还应注意标题层级(h1到h6)不要跳跃,页面地标区域(header、main、footer、aside)应正确使用,这些语义共同构成了屏幕阅读器用户的导航地图。
二、正确使用WAI-ARIA:只在没有选择时使用
当原生HTML无法表达复杂交互组件的语义时,就需要WAI-ARIA登场了。Tabs、Modal、Combobox、Tree等复合组件是ARIA的主要应用场景。使用ARIA时必须理解一个核心概念:ARIA只改变语义,不改变行为。也就是说,role="tab"能让屏幕阅读器知道这是一个标签页,但切换逻辑仍需开发者自己实现。
以Tabs组件为例,一个符合ARIA Tabs模式的实现需要包含三组关键属性:容器上的role="tablist",每个标签上的role="tab"和aria-selected状态,以及标签面板上的role="tabpanel"与aria-labelledby关联。示例结构如下:
<div role="tablist"> <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">首页</button> <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-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>
组件库中最容易踩的坑包括:只加role不加状态(如aria-expanded、aria-checked),动态更新后忘记同步ARIA属性,图片缺失alt属性,图标按钮没有aria-label。建议在组件库内部建立一套约定:所有交互状态必须映射到对应的aria属性,例如弹层组件必须维护aria-expanded,开关组件必须维护aria-checked,加载状态使用aria-busy。还可以考虑加入aria-live区域来播报动态消息,让屏幕阅读器用户感知到内容更新。
三、焦点管理与键盘导航:键盘用户的核心体验
键盘可访问性是组件库设计中最考验功力的部分。核心原则是:所有通过鼠标可达的交互,必须都能通过键盘完成。这要求组件库处理好三个层面:Tab顺序、焦点样式和焦点陷阱。
首先是焦点样式,千万不要通过outline: none移除焦点轮廓而不提供替代方案。如果默认轮廓与设计风格冲突,应该自定义:focus-visible样式,保证至少3像素宽度的明显视觉反馈。其次是Tab顺序,应遵循DOM自然顺序,避免滥用tabindex为正值。对于复合组件内部,推荐使用roving tabindex模式:容器内只有当前激活项的tabindex为0,其余为-1,通过方向键在内部项之间移动焦点。
// 简化的roving tabindex实现:方向键切换焦点
function handleKeyDown(e, items) {
const currentIndex = items.findIndex(el => el === document.activeElement);
let nextIndex;
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
nextIndex = (currentIndex + 1) % items.length;
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
nextIndex = (currentIndex - 1 + items.length) % items.length;
} else {
return;
}
items.forEach(el => el.setAttribute('tabindex', '-1'));
items[nextIndex].setAttribute('tabindex', '0');
items[nextIndex].focus();
}模态框组件则是焦点管理的另一个重点。打开时应将焦点移入弹窗内的第一个可聚焦元素,关闭时应将焦点归还给触发器元素。弹窗打开期间,Tab键必须被限制在弹窗内部(即焦点陷阱),否则键盘用户会迷失在弹窗背后的页面内容里。同时要监听Escape键关闭弹窗,并对背景内容设置aria-hidden以避免屏幕阅读器误入。
四、视觉无障碍与自动化测试保障
除了结构和交互,视觉层面的无障碍同样重要。组件库的默认配色必须满足WCAG对比度要求:正常文本对比度至少4.5:1,大号文本至少3:1。交互组件的焦点指示、禁用状态不能仅靠颜色区分,应辅以图标、下划线或文字说明。还要注意不要过度使用动画,为动画提供prefers-reduced-motion媒体查询的适配,尊重系统级的减少动态效果设置。
@media (prefers-reduced-motion: reduce) {
.fade-transition {
animation: none;
transition: none;
}
}最后,可访问性不能只靠自觉,必须用自动化测试兜底。推荐组合使用以下工具: axe-core可以集成到单元测试中,扫描组件渲染结果并报告违规项;eslint-plugin-jsx-a11y能在编码阶段拦截alt缺失、role误用等问题;Testing Library配合屏幕阅读器语义查询(如getByRole),能确保测试视角与辅助技术一致。组件库还应建立可访问性清单,每个新组件上线前逐项检查键盘操作、焦点行为、ARIA属性和对比度,形成团队级别的质量门槛。
总结来看,设计可访问性友好的组件库没有捷径,关键是在架构之初就把语义化HTML、ARIA规范、焦点管理和测试保障纳入设计约束。这些投入不仅服务残障用户,也让组件库的键盘体验、SEO表现和整体代码质量同步提升,是典型的多方共赢的工程实践。