应优先在自定义交互元素(如按钮、卡片、菜单项)中使用:focus-visible而非:focus,因其仅在键盘导航(tab等)时激活,避免鼠标用户出现干扰性焦点框,兼顾可访问性与体验。

什么时候该用 :focus-visible 而不是 :focus
直接替换 :focus 会破坏鼠标用户的体验——比如点击按钮后焦点框意外出现,干扰视觉流。浏览器只在用户**明确通过键盘触发焦点**(Tab、Shift+Tab、空格/回车激活控件)时才匹配 :focus-visible,而鼠标点击、触摸或程序化 .focus() 默认不触发它。这意味着你可以在保留鼠标用户“无焦点框”体验的同时,专为键盘用户强化可感知的焦点指示。
实际场景中,以下情况应优先考虑 :focus-visible:
- 自定义按钮、卡片、菜单项等需要键盘操作的交互元素
- 表单控件(
<input>、<select></select>)已自带合理焦点态,通常无需额外覆盖 - 第三方 UI 组件库未正确处理焦点逻辑时,用它兜底增强
:focus-visible 的兼容性与降级方案
Chrome 86+、Firefox 89+、Safari 15.4+ 原生支持;旧版 Safari 和 IE 完全不识别该伪类,会直接忽略整条规则。所以不能把它当唯一焦点样式来源。
推荐写法是「:focus + :focus-visible 叠加」,利用层叠优先级和浏览器特性自动降级:
button:focus {
outline: none;
}
button:focus-visible {
outline: 2px solid #007aff;
outline-offset: 2px;
}
效果是:老浏览器只执行第一行(移除所有焦点框),新浏览器在键盘聚焦时恢复 outline;鼠标点击按钮后,:focus 生效但 :focus-visible 不匹配,因此保持无 outline。
为什么有时 :focus-visible 死活不触发
常见原因不是代码写错,而是行为触发方式不对:
- 用鼠标点击后按空格/回车——这不算“键盘导航”,焦点仍是鼠标引发的,
:focus-visible不激活 - 在非可聚焦元素(如
<div>)上加 <code>tabindex="0"后,首次 Tab 进入可能不触发(部分浏览器需二次 Tab 才判定为键盘导航) - 页面加载后直接用键盘操作,但元素初始
tabindex="-1"或被display: none隐藏过,导致焦点逻辑紊乱 - 颜色对比度至少 3:1(相对于背景色),深色背景建议用亮蓝(
#007aff)、浅色背景可用深紫(#5b3bff) - 避免仅靠颜色区分——可叠加
outline-style: dashed或增加box-shadow提升辨识度 - 慎用
outline: none全局重置,它会直接砍掉所有键盘用户的焦点反馈
调试时可在控制台运行 document.activeElement 确认当前焦点是否落在目标元素上,再检查其是否满足可聚焦条件(tabIndex >= 0 且未被禁用)。
别忽略 outline 的可访问性细节
光有 :focus-visible 不够,outline 样式本身必须满足 WCAG 2.1 要求:
真正难的不是加一行 CSS,而是让每个可聚焦节点在任意操作路径下都稳定、可预测地暴露自己的状态。很多“不可访问”问题,根源在于 DOM 结构跳脱 tabindex 顺序,或用 JS 拦截了默认焦点行为却没补全语义。











