亲手修好 Tab 顺序
目标
编写一个只用键盘就能从头用到尾的页面。这里没有浏览器,但按下 Tab 时会到哪里,只凭标记就能计算出来。
为什么重要
键盘可访问性不是功能,而是顺序。什么能获得焦点,按什么次序循环,以及现在焦点在哪里是否看得见。而这三点用鼠标检查时永远不会暴露——QA 也是用鼠标做的,所以会原样上线。
尤其是正数 tabindex,它并不是把这个元素单独挪动,而是把页面分成两批。带正数的元素先全部循环一遍,其余的排在后面。只要一个 tabindex="1",这个元素就成了页面的第一站。
要创建的内容
只有一个文件:/root/work/kbd/app.html。焦点显示规则写在文档内的 <style> 中。
计算器
cd /root/work/kbd
python3 /opt/lab/checks/html-kbd-lab/focus.py app.html
它会用编号标出 Tab 顺序,并指出正数 tabindex、aria-hidden 内的可获得焦点的元素、仿制控件以及断开的 aria-* 引用。
步骤
- 把跳过链接放在第一站,并给
main加上tabindex="-1" - Tab 顺序 = 文档顺序(禁止正数 tabindex)
- 用原生元素替换仿制控件
- 装饰性内容用
aria-hidden,折叠区域用hidden - 展开按钮——
aria-expanded与aria-controls - 对话框——
role="dialog"、名称、关闭期间从 Tab 顺序中排除 - 焦点显示——
:focus-visible - 把计算器的结果保存为
08-focus.txt - 总结 →
09-notes.md
参考
- 不使用 JavaScript。本实验关注的是结构。
aria-hidden="true"不会阻止焦点。焦点会到达,却什么也读不出来,是最糟糕的状态。- 在真实页面上,把鼠标移开,只用 Tab 键使用 5 分钟,发现的问题比十个自动工具还多。
把跳过链接放在第一站
创建 /root/work/kbd/app.html,并在 <body> 中把 <a href="#main"> 跳过链接作为第一个可获得焦点的元素。目标 <main id="main"> 上必须有 tabindex="-1"。
mkdir -p /root/work/kbd。如果放在菜单之后,要跳过的内容已经全都经过了。而且如果目标是不能获得焦点的元素,浏览器只会改变地址,焦点依然原地不动——tabindex="-1" 不会让 Tab 键到达它,却能让链接使它获得焦点。
让 Tab 顺序与文档顺序一致
把可获得焦点的元素增加到六个以上(链接 3 个以上、按钮 2 个以上),并且不能有任何正数 tabindex。也不能给本来就能获得焦点的元素加上 tabindex="0"。
计算器会给出编号并标出来。只要有一个正数 tabindex,它们就会先全部循环一遍,其余元素排在后面。想改变顺序,请改变标记的顺序。
去掉仿制控件
放置三个以上 button 元素,并给它们全都设置 type。不能有加了 role="button" 的 div、带有 onclick 的 div/span,也不能有像 href="#" 这样哪里也去不了的链接。
role 只改变标签——焦点以及 Enter 和 Space 都不会随之而来。而且在表单中,没有 type 的 button 会变成提交按钮,点击后页面会刷新。要跳转就用链接,要执行操作就用按钮。
隐藏的东西是否真的被隐藏了
给不需要读出的装饰性元素加上 aria-hidden="true",并给折叠起来的面板加上 hidden。aria-hidden 内不能有可获得焦点的元素,折叠面板内的按钮必须从 Tab 顺序中移除。
aria-hidden 只会从辅助技术的树中删除——焦点依然会到达。焦点会到达,却什么也读不出来,是最糟糕的状态。只用于像用文字画出的图标那样既不获得焦点、也不需要读出的内容。
告知展开按钮的状态
放置一个带有 aria-expanded 和 aria-controls 的 button。aria-controls 必须指向真实存在的 id,并且当 aria-expanded="false" 时,该面板上必须带有 hidden。
如果只改变箭头的形状,状态就只会传达给用眼睛看的人。aria-expanded 是向程序说明“现在是否展开”。如果状态与界面不一致,两者之中就有一个在说谎。
给对话框命名
创建一个带有 role="dialog"、aria-modal="true" 和 aria-labelledby 的确认窗口,并在其中放两个按钮(执行、取消)。它一开始必须是 hidden,并且打开这个窗口的按钮必须通过 aria-controls 与之关联。
如果没有名称,即使打开了也只会读作“对话框”——请用 aria-labelledby 指向标题元素。而且在关闭期间,如果其中的按钮仍留在 Tab 顺序中,用户就会把焦点送到看不见的地方。
让焦点看得见
在文档内的 <style> 中编写 :focus-visible 规则,并设置可见的 outline。不能有用 outline: none 去掉焦点环的规则,并且跳过链接平时要在屏幕之外,在 :focus 时出现。
:focus 在鼠标点击时也会触发,而 :focus-visible 只有通过键盘到达时才会触发——这样既不会破坏设计,又能只对需要的人显示焦点环。outline: none 这一行是对可访问性破坏最大的一行。
留下 Tab 顺序
运行 focus.py,把结果保存为 /root/work/kbd/08-focus.txt。保存的停靠点数量必须与当前文档的 Tab 顺序一致,并且指出的问题必须为 0 条。
python3 /opt/lab/checks/html-kbd-lab/focus.py app.html | tee 08-focus.txt。请用眼睛对照一下,编号是否与屏幕上看到的次序相同——这是必须由人来做的确认。
什么会打乱顺序
在 /root/work/kbd/09-notes.md 中写三行以上(120 个字符以上)。正文中必须包含 tabindex、건너뛰기(韩文,意为“跳过”)、초점(韩文,意为“焦点”)。文档本身也必须仍然是 0 条问题。
核心只有一点——顺序由标记决定。 请用自己的话写下,正数 tabindex 为什么破坏的不是这一个元素,而是整个页面。