Flexbox 不负责键盘导航,仅提供语义清晰的布局基础;键盘友好性取决于 HTML 结构(nav/ul/li/a)、ARIA 属性(role="menu"、aria-expanded)和 JS 状态管理(焦点控制、ESC 回溯),并需避免 transform 隐藏导致焦点跳空或 max-height 动画阻断可聚焦性。

Flexbox 本身不处理键盘导航,但能为可访问的菜单提供稳定、语义清晰的布局基础。真正决定「是否键盘友好」的是 HTML 结构 + ARIA 属性 + JavaScript 状态管理,Flexbox 只负责让这些元素排得对、撑得稳、换行不乱。
HTML 结构必须用 nav + ul + li + a
不能用 div 堆出菜单,否则屏幕阅读器无法识别导航意图。浏览器默认会把 a 和 button 加入 tab 顺序,而 div 不会——哪怕加了 tabindex="0",语义也残缺。
-
nav是语义容器,告诉辅助技术「这是导航区域」 -
ul表示一组逻辑相关的选项,比div更准确表达层级关系 - 每个顶级菜单项用
li包裹a,子菜单也必须嵌套在对应li内(不是兄弟级) - 子菜单用
ul,且需加role="menu"和aria-hidden="true"初始隐藏
Flexbox 布局要避免打断 tab 顺序
常见错误是给 nav 或 ul 设 flex-direction: column 后,在移动端又用 transform: translateX(-100%) 把菜单移出视口——这会让键盘焦点「跳空」:按 Tab 键时焦点直接从 logo 跳到页脚,中间菜单项不可达。
- 隐藏菜单应优先用
visibility: hidden+position: absolute,而不是transform或left: -9999px - 若必须用
transform,务必同步设visibility: hidden和pointer-events: none,否则焦点仍可能进入但不可见 -
flex-wrap: wrap在小屏下慎用;文字过长时容易折行错位,破坏 tab 顺序线性,建议改用媒体查询切换为flex-direction: column
子菜单展开必须靠 JS 控制 aria-expanded 和 aria-hidden
Flexbox 无法响应 :hover 或 :focus-within 来控制多级菜单显隐——尤其键盘用户不会 hover,:focus-within 在 Safari 旧版和部分安卓 WebView 中支持差,且无法区分「聚焦子项」还是「聚焦父项」。
- 点击/Enter/Space 触发的展开,必须同步设置父
a的aria-expanded="true"和子ul的aria-hidden="false" - 子菜单出现后,第一个可聚焦元素(通常是首个
a)要用focus()主动获取焦点,否则键盘用户得连按多次 Tab 才能进去 - ESC 键关闭时,焦点应回到触发它的那个
a元素,不是随便丢回 body - 不要依赖
display: none隐藏子菜单——它会让所有子项彻底退出 tab 顺序,即使aria-hidden="false"也无效
键盘操作逻辑不能只靠 CSS 过渡
用 max-height + overflow: hidden 做展开动画很常见,但有个致命问题:键盘用户按 Down Arrow 想进子菜单时,如果动画还没结束,max-height 还没到实际高度,overflow: hidden 会裁掉下方内容,导致焦点「悬空」或跳到下一个非菜单元素。
立即学习“前端免费学习笔记(深入)”;
- 动画阶段不影响可聚焦性:子菜单一旦
aria-hidden="false",就必须立即可聚焦,不能等transition完成 - 更稳妥的做法是去掉高度过渡,改用
opacity+transform: translateY(),配合visibility: visible/hidden控制显示 - 若坚持用
max-height,初始值必须设为足够大的固定值(如max-height: 500px),而非0→fit-content,后者无法过渡
Flexbox 只是让这些逻辑更容易落地,而不是替代它们。


















