:focus-within 是最轻量的纯 css 键盘可访问下拉方案,但需父容器加 tabindex="0",且 safari 15.4+ 才稳定支持;须分开声明 :hover 和 :focus-within 规则,避免层叠冲突,并配合 js 实现 esc 关闭与屏幕阅读器支持。

导航菜单里用 :focus-within 实现键盘可访问的下拉展开
直接说结论::focus-within 是目前最轻量、无需 JS 就能让导航下拉菜单支持键盘操作的方案,但只在父元素获得焦点 *且* 子元素(如按钮或链接)处于聚焦态时才触发,不是“只要鼠标悬停就生效”。
常见错误现象是开发者把 :focus-within 当成 :hover 的替代品写在菜单容器上,结果键盘 Tab 到子项时下拉不出现——因为父容器本身不可聚焦,没加 tabindex="0"。
- 必须给导航项容器(比如
<li>或<div class="nav-item">)加上 <code>tabindex="0",否则它永远不会获得焦点,:focus-within永远不会匹配 - 子菜单(
<ul class="submenu"></ul>)默认用display: none,用.nav-item:focus-within .submenu切换为display: block或opacity: 1 - 注意 Safari 对
:focus-within的兼容性:iOS 15.4+、macOS Monterey+ 才稳定支持;旧版本会完全忽略,需配合 JS 回退 - 不要用
.nav-item:hover .submenu, .nav-item:focus-within .submenu { ... }合并写法,容易被浏览器解析为“任一条件满足即应用”,但实际行为不一致 - 推荐分开声明,并让
:focus-within规则在后面,确保它能覆盖:hover的隐藏逻辑:.nav-item:hover .submenu { opacity: 1; } .nav-item:focus-within .submenu { opacity: 1; } - 如果下拉菜单里包含可聚焦元素(如链接),焦点进入子菜单后,父容器仍保持
:focus-within状态;但如果用户按 Shift+Tab 返回到上一个导航项,焦点离开整个容器,状态就丢失了 - 给导航项添加
ontouchstart=""(空处理函数),可防止 Safari 在触摸后自动 blur 元素 - 或者用
element.focus({ preventScroll: true })在点击时手动聚焦容器,确保:focus-within被激活 - 别依赖
:focus-within做关键交互反馈(比如关闭菜单),它在部分 WebView 或低版本中不可靠;收起逻辑建议仍由 JS 控制 - 如果导航项超过 10 个,或子菜单含动态内容,建议保留轻量 JS 控制显隐,仅用
:focus-within做视觉 fallback - 必须用纯 CSS 方案时,记得给
.submenu加contain: layout style,减少重排范围 - 最容易被忽略的一点:没有 JS 时,用户无法按 Esc 关闭已展开的菜单——这是 WCAG 2.1 要求的交互模式,
:focus-within本身不提供关闭能力
:focus-within 和 :hover 共存时的层叠顺序问题
很多导航既要鼠标悬停展开,又要键盘聚焦展开,两个伪类一起写很容易出错。关键不是“能不能共存”,而是 CSS 规则的优先级和触发时机是否冲突。
典型场景是:鼠标悬停时下拉显示,但用户用键盘 Tab 进入后,移开鼠标,下拉却消失了——因为 :hover 失效,而 :focus-within 又没持续生效(比如焦点被意外移走)。
立即学习“前端免费学习笔记(深入)”;
移动端 Safari 下 :focus-within 不触发的隐性原因
在 iOS Safari 中,即使加了 tabindex="0",:focus-within 也可能不工作,不是 Bug,而是 Safari 对“聚焦”的定义更严格:它要求元素真正获得输入焦点,而不仅仅是被 Tab 导航选中。
常见错误是只给导航项加 tabindex="0",但没处理点击/触摸事件对焦点的影响。Safari 在触摸后默认不保留焦点,导致 :focus-within 瞬间失效。
用 :focus-within 替代 JS 展开逻辑时的性能与语义代价
看起来纯 CSS 更快,但实际有隐性成本:每次焦点变化,浏览器都要重新计算整个父容器的样式树,尤其当导航项嵌套深、子菜单结构复杂时,可能引发布局抖动或响应延迟。
更重要的是语义缺失——CSS 无法告诉屏幕阅读器“这个菜单已展开”,也无法监听“展开完成”事件来初始化子菜单里的第三方组件(比如地图或懒加载图片)。










