直接替换为原生select标签是最可靠方案——因其天然支持键盘导航、屏幕阅读器识别、表单提交和移动端系统级选择器调用,而手写div+ul存在焦点混乱、AR读不出状态、iOS失焦三大硬伤,重构时替换成本远低于修补。

直接替换为原生语义标签,比修补手写组件更可靠——尤其当项目已出现键盘导航失效、屏幕阅读器读不出状态或移动端失焦时,重构不是优化,而是止损。
为什么手写div+ul下拉菜单在重构期会卡死可访问性
老旧项目里常见的 div 套 ul+li 实现的“下拉”,在重构时暴露三个硬伤:
- 焦点管理混乱:
tabindex手动维护极易遗漏,Tab切入后无法用方向键选中,Enter无响应 - 屏幕阅读器读不出状态:缺少
role="combobox"、aria-expanded动态同步,或aria-selected没随点击更新 - 移动端闪退或失焦:iOS Safari 对非原生控件的
focus处理极不稳定,尤其在快速切换或弹出后滚动时
这些不是 JS 写得不够细的问题,而是 DOM 语义缺失导致浏览器无法接管基础交互逻辑。强行修补,成本远高于替换。
用原生 select 替换时必须守住的嵌套结构底线
select 能起作用,前提是 HTML 结构严格符合规范。浏览器会自动“修复”错误嵌套,但结果往往是选项消失或生成两个独立下拉框。
立即学习“前端免费学习笔记(深入)”;
- 只允许两种直接子元素:
option和optgroup -
optgroup必须包含至少一个option,且不能嵌套其他optgroup - 禁止在
select内写div、span、ul等非标准子节点 - 分组标题用
optgroup label="硬件",不要给它加disabled或期望它可点击
错误示例:<select><div><option>A</option></div></select> → 浏览器解析为分离的 <select></select> 和 <div><option>A</option></div>,选项彻底丢失。
保留样式定制需求的最小侵入式改造法
设计师说“必须用新 UI”,又不能放弃可访问性?不用推翻重写,只需两步:
- 把原生
select设为visually-hidden(用 CSS 隐藏但保留可访问性),例如:.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } - 用
button+ul构建视觉层,通过 JS 同步select.value和select.selectedIndex,并手动触发select.dispatchEvent(new Event('change', { bubbles: true }))
这样既保住了表单提交、键盘导航和屏幕阅读器支持,又让 UI 完全可控。关键点在于:所有用户交互最终都落回原生 select,视觉层只是镜像。
最容易被忽略的是 lang 和 main 的嵌套位置——<html lang="zh-CN"> 必须顶格出现在文件最开头,前面不能有任何空格或 BOM;<main> 必须是 <body> 的直接子元素,包在 <div class="wrapper"> 里会让 NVDA 直接跳过整个主内容区。



















