可访问组件实践
可访问组件实践 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能实现一个键盘可完整操作、角色与状态标注正确的标签页组件:role 命名、方向键移动、roving tabindex、aria-selected、可见焦点;并掌握一套「只用键盘」的验证流程。
问题场景
课程详情页的标签页(简介 / 章节 / 评价)第一版是三个 div 加 onClick:鼠标点了就切;键盘用户按 Tab 却只能「绕过」它们——div 不在焦点序列里;屏幕阅读器读出「文本、文本、文本」,不知道这是标签页;焦点 outline 又因为「不好看」被全局 CSS 删了。三类用户同时被挡在门外。
核心解释
按 WAI-ARIA 的 Tabs 模式补齐四层。
一、角色与状态标注——告诉辅助技术「这是什么、现在什么状态」:
<div role="tablist" aria-label="课程内容">
{tabs.map((t) => (
<button key={t.id} role="tab" id={"tab-" + t.id}
aria-selected={active === t.id}
aria-controls={"panel-" + t.id}
tabIndex={active === t.id ? 0 : -1}>
{t.label}
</button>
))}
</div>
<div role="tabpanel" id={"panel-" + active} aria-labelledby={"tab-" + active}>
{content}
</div>
底座用 button 而不是 div:button 天然可聚焦、可 Enter/Space 激活。无障碍第一原则是优先用语义化元素,不够再补 ARIA,而不是给 div 贴满属性。
二、roving tabindex:tablist 里只有当前活动 tab 的 tabIndex 是 0,其余是 -1——Tab 键「进入标签组」只停一次,组内用方向键移动,避免按 N 次才能穿过。
三、键盘行为:左右方向键移动并循环,Home/End 跳两端;「移动即切换」(自动激活)或「Enter 确认」(手动激活)二选一并保持一致:
function onKeyDown(e, id) {
const idx = tabs.findIndex((t) => t.id === id);
if (e.key === "ArrowRight") { e.preventDefault(); activate(tabs[(idx + 1) % tabs.length].id); }
if (e.key === "ArrowLeft") { e.preventDefault(); activate(tabs[(idx - 1 + tabs.length) % tabs.length].id); }
}
四、可见焦点:绝不裸删 outline;要改样式就给 :focus-visible 换一个更醒目的替代(粗外框、加粗)。焦点看得见是键盘可用的前提。
验证流程全程不碰鼠标:Tab 进入 → 方向键逐个切换 → 每一步焦点可见、面板内容跟随 → Tab 离开,焦点落到面板内容。
示例拆解
实验区是补齐后的三标签课程页:
- 语义结构:role=tablist/tab/tabpanel,aria-selected 随激活变化,tab 与 panel 用 id 互相关联;
- roving tabindex:Tab 只进入一次,方向键在标签间循环;
- 焦点环:聚焦的标签显示明显的琥珀色外框(模拟 :focus-visible 的替代样式);
- 面板内容随激活即时切换;
- 验证:按提示「只用键盘」走完整旅程,再用 DevTools 的无障碍面板查看角色树——读出的应是「标签页」结构而不是三个文本。
动手任务
打开本课实验区:
- 加 Home / End 键:焦点与选中跳到第一个 / 最后一个标签。
- 把「移动即切换」改成「Enter 确认切换」的手动激活模式:方向键只移焦点,面板暂不切换。
- 用 DevTools 的无障碍(Accessibility)面板检查 tablist 角色,确认读出的结构与语义一致。
常见错误
- div + onClick 充当按钮:不在焦点序列、读不出角色;用 button,或补 role + tabIndex + 键盘处理。
- 每个 tab 都 tabIndex=0:键盘用户要按 N 次才能穿过标签组;用 roving tabindex。
- 删掉 outline 却不给替代:焦点不可见等于对键盘用户隐身;用 :focus-visible 提供更醒目的样式。
- aria-selected 标到面板上:它是 tab 的状态;面板用 aria-labelledby 关联对应标签。
检查与复盘
自查清单:
- 不碰鼠标完成「进入、切换、离开」三个环节
- 角色与状态标注齐全,无障碍树读出的是标签页结构
- 焦点全程可见,没有裸删 outline
参考资料
- MDN:ARIA 基础 —— 角色、状态、属性的体系
- WAI-ARIA Authoring Practices:Tabs 模式 —— 键盘交互的权威模式
- MDN:tab 角色 —— tab/tablist/tabpanel 的属性约定
读完这一课了?
标记完成后,工作台会安排接下来的学习。