:focus样式出问题主因是导航容器(如)不可聚焦且未配role/keydown监听,而非样式错误;应聚焦语义元素,禁用正数tabindex,:focus与:focus-visible须共存且:focus在前。

为什么导航栏的 :focus 样式总出问题
不是样式没写对,而是浏览器对 <nav></nav> 内部元素的焦点行为有隐含规则:原生可聚焦元素(如 <a></a>、<button></button>)默认进 Tab 流,但容器本身(如 <nav></nav>、<ul></ul>、<li>)不自动可聚焦——你给 <li> 加 tabindex="0",却没配 role 或 keydown 监听,键盘用户按到它就卡住,啥也不发生。
常见错误现象包括:
- 鼠标点击后没焦点反馈,
:focus-visible完全不触发 - Tab 到某个菜单项时页面突然滚动,视口跳动
- 下拉菜单展开后,方向键无法控制子项,或按 Tab 直接跳出整个导航区域
根本原因在于混淆了“谁该被聚焦”和“谁该响应键盘”。导航栏里真正需要键盘操作的是链接(<a></a>)和按钮(<button></button>),不是 <li> 或 <ul></ul>。
<a></a> 和 <button></button> 的 tabindex 怎么设才安全
原生语义元素不需要额外加 tabindex。加了反而破坏 SSR/hydration 时的焦点同步,也容易让辅助技术误判。
立即学习“前端免费学习笔记(深入)”;
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
-
<a href="/home">首页</a>:默认可聚焦,删掉所有 tabindex="0" -
<button type="button">菜单</button>:同上,加tabindex="-1"会主动踢出 Tab 流,除非是 JS 动态聚焦目标(比如下拉触发后聚焦首项) -
<div role="button" tabindex="0">折叠面板</div>:这是唯一需要tabindex="0"的场景,但必须同步监听keydown响应Enter和Space,并设role="button"
正整数 tabindex(如 tabindex="1")必须彻底删除——它不保证顺序稳定,且会让焦点流在 React/Vue 中不可预测。
下拉菜单的方向键导航怎么不卡顿
方向键(ArrowDown、ArrowUp)必须在 keydown 阶段拦截,且优先用 event.key 判断。在 keyup 处理会导致页面已滚动、你的逻辑失效。
- 监听容器的
keydown,不是每个子项单独监听(性能差、易漏) - 第一时间调用
event.preventDefault(),否则ArrowDown会触发页面滚动 -
Home/End键要直接跳转到首项/末项,别依赖连按多次Arrow键 - 缓存当前聚焦索引,避免每次方向键都重新
querySelectorAll查 DOM - Tab 离开下拉区域时,用
focusin+container.contains(document.activeElement)检测,手动focus()回内部首项或上一次活跃项
注意嵌套:外层导航栏不该拦截内层下拉菜单自己的焦点转移,可用 data-focus-lock 类做层级标记。
:focus 和 :focus-visible 样式怎么共存不打架
只写 :focus-visible 会导致鼠标点击无反馈;只写 :focus 又会让所有聚焦都套蓝框,视觉混乱。二者必须共存,且顺序不能错。
-
a:focus应设轻量提示,例如outline: 1px dotted #999;或box-shadow: 0 0 0 2px rgba(0,122,255,0.3); -
a:focus-visible必须显式覆盖outline,并加outline-offset: 2px避免与相邻元素粘连 - 绝对不要写
*:focus { outline: none; }—— 它会干掉:focus-visible的样式 - 自定义按钮(
div[role="button"])需监听keydown才能激活:focus-visible,否则 Chromium 可能拒绝匹配
移动端真机测试容易被忽略:iOS 点击会先触发 :active 再短暂触发 :focus,叠加系统点击高亮,导致样式冲突。用 * { -webkit-tap-highlight-color: transparent; } 去掉高亮,但别用 touchstart + blur() 破坏键盘流。


















