hidden属性+role="tab"+键盘事件接管是唯一满足表单不丢值、媒体自动暂停、读屏器准确跳过且Safari/VoiceOver可用的方案;display:none仅视觉隐藏,DOM仍在运行,导致焦点丢失、video持续缓冲、读屏器误读且无障碍树为空。

用 hidden 属性 + role="tab" + 键盘事件接管,是目前唯一能同时满足表单不丢值、媒体自动暂停、读屏器准确跳过且 Safari/VoiceOver 可用的方案。
为什么不能只靠 display: none 切换?
它只是视觉隐藏,DOM 仍在运行:含 <input> 的面板切走后,再切回时焦点丢失、滚动位置重置;<video></video> 在 Safari 下持续缓冲甚至静音播放;屏幕阅读器仍可能读出内容(尤其没配 aria-hidden 时)。更关键的是,display: none 元素根本不在无障碍树里——你在 Chrome DevTools 的 Accessibility 面板里选中它,三列数据(Computed Role/Name/States)会直接为空。
常见错误现象:
- 用户按 Tab 进入标签区后,方向键无法切换页签
- VoiceOver 读出已隐藏面板里的文字
- 切换后
<iframe></iframe>重新加载,登录态丢失
必须写的 ARIA 结构和属性
结构不是可选项,是读屏器识别逻辑的基础。漏掉任意一项,键盘用户就卡死在第一个按钮上。
立即学习“前端免费学习笔记(深入)”;
- 外层容器加
role="tablist",不能用<div> 或 <code><ul></ul>纯样式模拟 - 每个页签用
<button type="button" role="tab"></button>,禁用默认表单提交行为 - 按钮必须带
aria-controls="panel-home",指向对应面板 ID - 面板写成
<div id="panel-home" role="tabpanel" aria-labelledby="tab-home">,<code>aria-labelledby必须指回按钮 ID - 激活页签设
aria-selected="true",非激活页签设aria-selected="false" - 所有面板设
element.hidden = true,目标面板设false - 所有页签按钮设
aria-selected="false",目标按钮设"true" - 非激活面板加
aria-hidden="true",激活面板设"false"(hidden属性本身已让读屏器跳过,但双重保险更稳) - 切换完成后,调用
document.querySelector('#panel-home').focus(),聚焦到面板首个可交互元素(如第一个<input>) - 给
role="tablist"容器绑定keydown事件,监听e.key === 'ArrowRight'等 - 维护一个按钮数组,用
Array.prototype.indexOf()找当前焦点索引 - 箭头越界时循环:到末尾再按右箭头 → 跳回第一个
- Safari + VoiceOver 下,Enter/Space 可能触发两次(按钮原生 + JS 监听),需加防重复执行标记
- 小屏下若 tab 横向布局被挤成下拉菜单,必须动态把
role="tablist"改为role="combobox",否则键盘逻辑彻底失效
切换时 JS 必须同步更新的三项
只改 hidden 或只改 class,读屏反馈就会错乱。Chrome 和 Safari 对 role="tab" 的键盘支持不一致,必须手动同步状态。
键盘导航不能只靠原生 Tab 键
Tab 键负责进入/离开 tab 区域,左右箭头、Home/End 才负责在页签间切换——这个逻辑必须由 JS 实现,浏览器不自动提供。
实操要点:
最易被忽略的点:首次加载时,**默认激活面板的 hidden 属性必须手动移除**,否则白屏;hidden 是 HTML5 原生语义,IE 不支持,如需兼容,得回退到 display: none + 手动管理 tabindex 和媒体状态。



















