select 必须严格嵌套 option 或 optgroup,禁止混入其他标签;多级菜单需语义化嵌套 ul/li;联动下拉用两个独立 select+js;移动端需 js 替代 hover;name 属性和 dom 结构正确性至关重要。

select + option 必须严格嵌套,不能夹杂任何其他标签
浏览器只接受 <select></select> 的直接子元素为 <option></option>、<optgroup></optgroup> 或空白符。一旦混入 <div>、注释、文本节点甚至换行缩进过多(某些旧版 IE 对空白敏感),解析器会把非法内容移出 <code><select></select>,结果是选项消失或生成两个孤立 DOM 节点。
常见错误写法:<select><div><option>A</option></div></select> → 实际渲染为 <select></select><div><option>A</option></div>,表单完全失效。
-
<optgroup></optgroup>只能作为<select></select>的直接子元素,不能嵌套,也不能被选中 - 想禁用某组选项,必须给每个
<option></option>单独加disabled,而不是给<optgroup></optgroup>加 - 空提示项推荐写成:
<option value="" disabled selected>请选择</option>,显式声明值、禁用状态和默认选中
多级悬停菜单必须用嵌套 ul 结构,子 ul 必须是父 li 的直接子元素
用 <div> 堆砌多级菜单看似简单,但会导致 CSS <code>:hover 选择器失效、键盘导航中断、屏幕阅读器无法识别层级。真正可靠的结构是语义化嵌套:<ul></ul> 表示菜单容器,每个 <li> 是菜单项,子菜单必须是该 <li> 的直接子 <ul></ul>。
错误结构:子 <ul></ul> 放在父 <li> 外面,或用 <span></span> 包裹箭头导致点击区域错位;正确结构中,<li> 同时承载触发行为与容器职责。
- 一级菜单
<ul></ul>建议加role="menubar",内部<li>加role="menuitem" - 有子菜单的项加
aria-haspopup="true"和aria-expanded="false" - CSS 定位必须无缝:父
<li>设position: relative,子<ul></ul>设position: absolute并紧贴底部(top: 100%),避免鼠标移动途中触发闪退
两级联动下拉菜单必须用两个独立 select 元素 + JS 动态更新
HTML 原生不支持 <select></select> 嵌套联动。所谓“两级”,本质是两个分离的 <select></select>:一个控制源(如省份),一个受控目标(如城市)。JS 监听前者 change 事件,清空并重写后者选项。
直接操作 innerHTML 是高危操作:会丢失已绑定事件、引发 XSS 风险、破坏 DOM 引用。也不应只追加不清理,否则选项重复累积。
- 清空二级
<select></select>推荐用循环removeChild或options.length = 0,而非innerHTML = "" - 插入新
<option></option>应用appendChild()或DocumentFragment批量插入,避免频繁重排 - 初始状态需设默认提示项:
<option value="" disabled selected>请选择城市</option>,且value必须唯一、不可为空格或纯文本 - 三级联动不是简单叠加监听器,而是链式依赖:区级
<select></select>初始应设disabled和aria-disabled="true",仅当市级value非空才发起数据请求
移动端 hover 失效是必然的,不能只靠 CSS
所有纯 :hover 实现的下拉菜单,在 iOS Safari 和多数 Android 浏览器上基本不可用——touch 设备没有 hover 状态,第一次点击后模拟一次 hover 属于兼容性 hack,不可靠且破坏体验。
键盘用户也无法通过 Tab 进入子菜单,除非补足 focus-within 或 JS 控制类切换,并同步更新 aria-expanded 和 aria-hidden。
- 移动端必须用 JS 监听
click或focus,切换aria-expanded和显示状态,同时确保子菜单可聚焦、可键盘导航 - 隐藏子菜单时优先用
opacity: 0; visibility: hidden; pointer-events: none;,而非display: none,便于过渡动画且不破坏布局流 - 若父容器用了
transform、perspective或will-change,会创建新层叠上下文,导致position: absolute的子菜单定位基准偏移——明明写了top: 100%,却飘到页面左上角
<select></select> 缺 name 属性导致表单提交时字段直接消失,以及嵌套 <ul></ul> 中子 <ul></ul> 不是父 <li> 的直接子元素——这两处错位不会报错,但会让功能在特定设备或辅助技术下彻底失效。











