:focus 与 :focus-visible 必须分开使用;:focus 匹配所有焦点状态,:focus-visible 仅在键盘聚焦时生效,确保键盘用户获得专属焦点反馈,提升可访问性。

:focus 与 :focus-visible 必须分开用,否则键盘用户会丢失焦点反馈
只写 :focus 会导致鼠标点击和键盘 Tab 进入时样式完全一致,破坏可访问性。浏览器默认行为是:鼠标点击输入框会触发 :focus,但不触发 :focus-visible;而键盘 Tab 或 Shift+Tab 切换时,两者都会触发(取决于 UA 样式)。关键在于:你不能靠 :focus 区分操作方式,必须用 :focus-visible 做键盘专属样式。
-
:focus是通用焦点状态,所有获得焦点的元素都匹配,无论来源 -
:focus-visible是“仅当键盘聚焦时”才生效,现代浏览器(Chrome 86+、Firefox 89+、Safari 15.4+)已原生支持 - 如果要兼容旧版 Safari(js 检测
keyboard-focus并加 class,但纯 CSS 场景下建议接受渐进增强 - 不要把
:focus-visible当作:focus的子集来覆盖写,比如input:focus { outline: none; } input:focus-visible { outline: 2px solid blue; }是安全的;反过来写会失效
:active 不等于点击完成,它只是“按下瞬间”的临时状态
:active 在鼠标左键按下、或触摸屏触控开始但尚未抬起时激活,松开即消失。它不表示“点击成功”或“已响应”,也不等同于 JavaScript 的 click 事件。很多开发者误以为给按钮加 :active { transform: scale(0.95); } 就能模拟点击反馈,结果发现移动端长按、PC 上右键、甚至键盘回车都无法触发——因为 :active 只响应主指针按下动作,且不区分按键类型。
-
:active无法捕获键盘 Enter/Space 触发的激活行为 - 在触摸设备上,
:active通常有约 300ms 延迟(受点击延迟影响),除非已禁用touch-action: manipulation - 若需统一点击反馈,应结合
:active+:focus+ JS 的click类名控制,而非只依赖伪类
为什么 :hover + :active 组合在移动端不可靠
移动端没有“悬停”概念,但浏览器为兼容旧 CSS,会在第一次触摸目标时模拟一次 :hover,紧接着触发 :active,然后才是 :focus。这意味着:一个按钮在 iOS Safari 上被点一下,可能依次经历 :hover → :active → :focus,中间无间隔;而在 Android Chrome 中,:hover 可能根本不触发。这种不一致性会让纯 CSS 的“点击态”视觉反馈错乱或闪烁。
- 不要用
a:hover + a:active实现“按下即变色”的连贯动效,移动端大概率只闪一下 - 若必须保留悬停反馈,优先限制在
@media (hover: hover) and (pointer: fine)媒体查询内使用 - 对表单控件(如
input[type="checkbox"]),:active几乎无效,应改用:checked配合 label 控制视觉
实际可落地的组合方案
真正能区分操作意图的最小可行 CSS 写法是:
立即学习“前端免费学习笔记(深入)”;
button {
/* 默认态 */
background: #eee;
}
button:focus {
/* 所有焦点场景共用基础反馈,比如加边框 */
outline: 2px solid #007bff;
}
button:focus:not(:focus-visible) {
/* 鼠标点击获得焦点时,移除 outline(避免干扰)*/
outline: none;
}
button:focus-visible {
/* 键盘聚焦时才显示高亮 outline */
outline: 2px solid #007bff;
outline-offset: 2px;
}
button:active {
/* 仅鼠标/触摸按下瞬间压感反馈 */
transform: scale(0.98);
}注意 :focus:not(:focus-visible) 这个否定组合——它是让鼠标点击不显示焦点环的关键。这个技巧在主流框架(如 React Aria、Headless UI)中已被广泛采用,但容易被忽略的是:它依赖浏览器对 :focus-visible 的正确实现,而某些自定义 focus 管理(如手动 el.focus() 后未设置 preventScroll)可能绕过该逻辑。


















