不能用<select>实现多级下拉导航,因其仅支持单层结构、无法嵌套、不响应hover/键盘/点击子项;必须采用语义化<ul><li>嵌套结构配合CSS显隐与JS交互,并严格遵循ARIA角色规范和焦点管理。

用 <select> 实现多级下拉导航根本走不通——它不支持嵌套、hover 展开、键盘导航进子项,也不响应点击二级选项。真要可用,必须用 <ul> 嵌套结构配 CSS 显隐 + 少量 JS 补交互。
为什么不能用 select 做多级菜单
<select> 是单层表单控件,浏览器强制压平所有选项。哪怕你硬塞 <optgroup>,子项仍被展平显示,<optgroup> 内不能嵌 HTML、无法 hover、不能绑定 click 到具体子项,更别提键盘 Tab 进入二级菜单。常见错误是写 document.getElementById('city').innerHTML = '...',这会直接销毁已绑定的事件监听器,且存在 XSS 风险。
ul/li 嵌套结构怎么写才不出错
子 <ul> 必须是父 <li> 的**直接子元素**,否则 CSS 选择器(如 li:hover ul)失效,键盘导航中断,屏幕阅读器读错层级。正确结构示例:
<ul role="menubar">
<li role="menuitem" aria-haspopup="true" aria-expanded="false">服务
<ul role="menu">
<li role="menuitem">咨询</li>
<li role="menuitem">实施</li>
</ul>
</li>
</ul>
- 外层
<ul>加role="menubar",每个一级项加role="menuitem" - 有子菜单的
<li>必须同时带aria-haspopup="true"和aria-expanded="false" - 子
<ul>加role="menu",其子<li>加role="menuitem" - 不要把子菜单写在父
<li>外面,也不要套<div>包裹
纯 CSS :hover 实现的三个硬伤
桌面端看似能用 li:hover ul { display: block; },但实际踩坑密集:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 移动端完全失效:iOS/Android 上
:hover不可靠,首次点击后才模拟一次,且无法键盘操作 - 鼠标移入途中闪退:一级和二级菜单之间若有像素间隙(比如
border或margin),:hover立刻中断 - 焦点不可达:Tab 键无法进入子菜单项,视障用户直接跳过整个区域
- 定位错乱:父容器若用了
transform、perspective或will-change,子菜单会飘到页面左上角
解决办法不是死磕 :hover,而是用 JS 切换 aria-expanded 和 class,并监听 keydown(Enter/Space)与 focusin 事件。
移动端和键盘用户怎么兼容
纯 :hover 方案对触屏和键盘用户等于不存在。必须补 JS 逻辑:
- 点击父项时,先关闭其他所有展开项,再切换当前子菜单显隐
- 同步更新
aria-expanded值,确保屏幕阅读器能感知状态变化 - 为可聚焦元素(如
<a>或<button>)加tabindex="0",并处理Enter和Space键触发展开 - 用
focusin/focusout模拟 hover 效果,避免依赖 pointer-events - 禁用
pointer-events: none隐藏子菜单——它会让焦点丢失,键盘用户进不去
最易被忽略的是:子菜单 <ul> 默认应设 aria-hidden="true",展开时改为 "false";未选省时,市下拉框必须 disabled 并加 aria-disabled="true"。这些不是“锦上添花”,是让菜单真正可用的底线。


















