焦点顺序应随可见的动态变化(如展开、隐藏)实时更新
别名: 展开后的 Tab 序列 · 隐藏项仍可聚焦 · accordion focus
概念解释
可 Tab 的集合不是页面加载时拍好的一张名单,而是当前可见对象的活集合。手风琴展开,里面的链接和字段必须立刻进入序列;再收起,它们必须立刻离开。选项卡切走、折叠面板关上、移动端菜单收起,同一条规则。顺序不随可见性改写,人就会 Tab 进一块看不见的区域,或永远走不到刚展开的内容。
这和「新节点插入后焦点停在原按钮」不是同一件事。那是焦点停在哪;这是名单里还有谁。
机制
从键盘的角度看,节点有三种不同的「不在」:display: none / hidden 通常会把它移出序列;visibility: hidden 或透明度为 0 的层,视觉没了,焦点往往还在;aria-hidden 只改辅助技术树,不自动改 Tab。离屏菜单用负坐标藏在视口外,对指针等于没有,对 Tab 仍是一长串真实控件。
展开失败的镜像是:内容已经画出来,却还带着 tabindex="-1" 或父级 inert 没摘掉,视觉上的新字段键盘进不去。产品把「可见」做成了样式开关,把「可到达」留在了上一次的状态。两套开关不同步,活集合就冻在过期名单上。
怎么研究
对每一类披露控件做两次遍历:收起态 Tab 一周,记下落点;展开后再 Tab 一周,比较集合差。应收起却仍能聚焦的,记「幽灵停靠」;应展开却进不去的,记「名单未写入」。
移动端汉堡菜单特别容易冻名单:关上之后图标消失,序列里还留着那些链接,焦点会跑到视口外。把这个状态单独测。
边界
预渲染但尚未进入当前步骤的向导页,应当不可聚焦,即使 DOM 里已经有字段。轮播里不在当前帧的幻灯,同样不应停靠。相反,对屏幕外但仍「逻辑可见」的内容(例如可滚动容器里尚未滚到的条目)应保持在序列中——那不是隐藏,是溢出。inert 和 aria-hidden 叠在动画过程中会有几帧不同步,动画结束时集合必须已经改完,不能把修正推迟到下一次点击。
怎么落地
- 隐藏即移出序列:用
hidden、display: none或inert,不要只靠透明度或挪到屏外。 - 展开完成的同一时刻摘掉
inert/ 负 tabindex,让新字段可到达。 - 验证:打开再关上导航抽屉或手风琴,只按 Tab。关上后焦点若走进空白,或打开后新链接要绕很远才到,活集合没有跟着改。