R3.10.2focus-visible设计

焦点可见性不能依赖是否使用鼠标

别名: focus-visible · 焦点环 · 输入模态 · keyboard modality · :focus

概念解释

焦点指示画不画,必须按这一次聚焦是怎么发生的来决定,而不能按「这人用过鼠标」这种会话级判断来关死。鼠标点击会把焦点移到按钮上,许多人因此把 :focus { outline: none } 写成全局,于是稍后改用键盘、开关或语音时,停在哪一个控件上已经没有任何记号。:focus-visible 把「这次是否应画出焦点」交给输入模态:键盘等效的聚焦要画,指针点击的那一次可以不画——下一次 Tab 仍必须画。

它处理的是显示条件与模态的绑定,不是描边在各种背景上够不够清楚,也不是去掉默认样式之后有没有替代。

机制

焦点是文档级的活动元素,与最近一次输入装置无关。鼠标点击、触摸、Tab、空间方向键、屏幕阅读器手势,都会改 document.activeElement。若可见性写成「检测到 mousemove 就给 html 加 class,从此隐藏所有 outline」,模态被冻成会话旗帜:用户放下鼠标去键盘,旗帜不会自动翻回来。即使翻回来,中间那几次键盘聚焦已经没了记号。

:focus-visible 的启发式跟的是单次聚焦事件:由键盘或键盘等效(含部分辅助技术)触发则匹配,由指针点击触发则常常不匹配。它不是「用户是键盘用户」的身份标签。同一人可以点完再 Tab,两次聚焦的可见性必须允许不同。用 :focus-within 在容器上画一圈,或只用 hover 加深来冒充焦点,会把指针态和键盘态混成一种,鼠标划过的高亮无法告诉你焦点现在在哪。

边界

始终显示焦点环在触控为主的界面上会显得吵,但「吵」不能推出「检测到过触摸就永久关闭」。强制始终可见的高对比模式、部分系统设置会要求环一直在,:focus-visible 不得盖过这些设置。脚本 element.focus() 是否匹配 :focus-visible 因浏览器而异,打开浮层后的程序聚焦不能假定环一定出现或一定不出现,必要时用可见的选中态同时表达。画环的对比和厚度是另一层问题:条件对了但环溶进背景,用户同样看不见。IME、游戏手柄、开关控件各有自己的模态,不能只测鼠标对键盘这一对。

怎么落地

  • 去掉默认 outline 时,用 :focus-visible(或等价的逐次模态探测)决定何时重画,禁止按「本会话用过鼠标 / 触摸」隐藏全部焦点记号。
  • 不要用 hover 填充或 :focus-within 容器描边代替控件自己的焦点指示。
  • 程序化 focus() 之后,检查当前引擎会不会画出可见指示;不会的话,给获得焦点的控件一个不依赖伪类的选中外观。
  • 验证:只用来鼠标点一遍主路径,确认点击态可以没有环;然后不刷新页面,改用键盘 Tab 走同一路径,每一个可聚焦控件都必须出现焦点记号。再打开系统高对比或「始终显示焦点」类设置,确认环没有被站点样式吃掉。

延伸

  • 同组R3.10.1 浮层内的焦点需被限制在可交互范围 · R3.10.3 元素被移除时焦点需要有承接目标
  • 相邻J2.06 焦点可见性 · R3.02 焦点管理
  • 站内检索focus-visible · input modality · focus indicator · :focus

同组卡片

快捷操作

分享

分享当前页面

ios_share

https://hci.top/zh/handbook/R3.10.2