:focus-visible 必须与 :focus 配合使用,单独使用会导致鼠标用户无反馈、旧浏览器完全失效;书写顺序需 :focus 在前兜底,且自定义元素须满足 role、keydown 响应等条件,并依赖 js-focus-visible polyfill 兼容。

不能只靠 :focus-visible 兼容辅助设备,它必须和 :focus 配合使用,且顺序、语义、交互逻辑缺一不可。
为什么单独写 :focus-visible 会破坏辅助访问
浏览器只在明确的键盘导航行为(Tab、Shift+Tab、Enter、Space)下才匹配 :focus-visible;鼠标点击、触摸 tap、甚至 JS 调用 element.focus() 都不会触发它。只写 button:focus-visible { outline: 2px solid blue; } → 鼠标用户点按钮后完全没视觉反馈,键盘用户按 Tab 时样式虽对,但鼠标用户无法确认是否操作成功,尤其在密集工具栏中极易误操作。
更关键的是:Safari 15.3 及更早版本、IE 全系、部分政务/银行 WebView,压根不认识 :focus-visible 这个语法,整条 CSS 规则直接被解析器跳过——不是样式不生效,是根本没加载。此时若没 :focus 兜底,所有浏览器里焦点都不可见。
:focus 和 :focus-visible 的书写顺序与取值逻辑
顺序错了,降级路径就断了。浏览器不支持 :focus-visible 时,它整条规则被丢弃,但前面的 :focus 仍有效——前提是它写在前面。
-
button:focus应设为轻量反馈,比如outline: 1px dotted #999;或box-shadow: 0 0 0 2px rgba(0,118,255,0.2);,避免干扰鼠标用户 -
button:focus-visible必须显式覆盖outline,且带outline-offset: 2px—— 工具栏图标间距常小于 4px,不加 offset 容易和相邻元素粘连 - 绝对不要写
*:focus { outline: none; },这会把:focus-visible的样式也干掉;若需重置,只针对具体选择器,如button:focus { outline: none; }
自定义组件(如 <div role="button">)怎样真正支持 <code>:focus-visible
加 tabindex="0" 不够。浏览器只对“语义明确 + 交互可靠”的元素启用该伪类判断,必须同时满足:
- 有明确 ARIA role:
role="button"、role="link"等,不能只是tabindex="0"的<div> <li>监听 <code>keydown事件,显式响应Enter和Space,否则 Chromium 可能判定为“非交互意图”,拒绝激活:focus-visible - 确保元素可聚焦:不能被
pointer-events: none或visibility: hidden阻断,否则 js-focus-visible Polyfill 也拦不到事件 - 放在
底部或加defer,否则可能错过初始聚焦 - 它在原生支持的浏览器里静默退出,在不支持的环境里注入
js-focus-visible类,并给当前聚焦元素加focus-visible类 - 手写 JS 判断“是否键盘触发”几乎必然失败,因为浏览器内部启发式逻辑(比如首次交互是鼠标还是 Tab)不公开、不稳定,且移动端 Safari 对
.focus()调用限制极严
contenteditable 容器自身不触发 :focus-visible,需手动加 tabindex="0",并在 focusin 中通过 JS 切换 .focus-visible 类模拟。
旧浏览器兼容必须用 js-focus-visible Polyfill
@supports selector(:focus-visible) 在 Safari 15.3 及更早版本中解析失败,整个 CSS 块会被丢弃——你连降级样式都拿不到。唯一可行路径只有引入 js-focus-visible:
最易被忽略的一点:Polyfill 不会自动识别你写的 <div tabindex="0">,必须补上 <code>role="button" 并监听 keydown,否则它不会标记该元素为 focus-visible。











