原生 popover 不能做多级联动下拉菜单:它不支持 hover 展开、键盘导航、嵌套结构及状态链式控制,无法实现“省→市→区”三级联动,根本原因是其仅为单点弹出机制,缺乏菜单所需的焦点流、展开同步与父子依赖管理能力。

原生 popover 根本不能做多级联动下拉菜单
直接说结论:popover 属性不是为菜单设计的,它不支持 hover 展开、键盘导航、嵌套结构或状态链式控制——所有试图用它实现“省→市→区”三级联动的尝试,都会卡在第一层就失败。
常见错误现象:popover 元素一旦显示,就脱离 DOM 流、无法被 position: absolute 精确锚定到子项下方;点击二级项时,一级 popover 不会自动收起;aria-expanded 和 role="menu" 完全无效,屏幕阅读器读不出层级关系。
根本原因在于:popover 是声明式浮层机制,只处理“单点弹出/隐藏”,没有菜单所需的焦点流、展开状态同步、事件委托或父子依赖管理能力。浏览器不会把它当作菜单容器渲染,也不会响应 ArrowDown 或 Tab 导航。
为什么不能用 popovertarget 嵌套触发
popovertarget 只能指向一个已声明 popover 的元素,且目标必须是顶层可显示节点(如 div),不能是另一个 popover 内部的按钮——浏览器会忽略嵌套的 popovertarget,也不允许在已显示的 popover 里再调用 showPopover()。
立即学习“前端免费学习笔记(深入)”;
实操限制:
- 二级按钮加
popovertarget="city-popover",但city-popover在父级 popover 关闭前无法激活(浏览器强制单层浮层) - 即使强行用 JS 调用
document.getElementById('city-popover').showPopover(),也会触发DOMException: Cannot show a hidden popover - 移动端 Safari 对连续 popover 调用支持极差,常直接静默失败
真正轻量又可行的替代方案
放弃 popover,改用 20 行以内的 JS + ul/li 结构,既保持原生语义,又可控可访问:
关键点:
- 用
button替代select作为触发器,加role="combobox"和aria-expanded - 每级菜单用
ul[role="menu"],选项用li[role="menuitem"],并动态更新aria-disabled - 清空逻辑走手动遍历:先解绑
change类事件(避免误触发 fetch),再重置value、清空innerHTML、设disabled - 定位靠
getBoundingClientRect()计算,不依赖transform容器,避免层叠上下文错位
这样做的好处是:键盘 Tab 可进入、Arrow 键可导航、iOS Safari 兼容稳定、CSS 定制自由,且代码体积比引入 tippy.js 小一个数量级。
最容易被忽略的兼容性陷阱
不是功能写不出来,而是细节让菜单在真实设备上失效:
比如 iOS Safari 中,仅设 disabled 不足以阻止触摸操作,必须同时加 pointer-events: none 和 aria-disabled="true";又比如用 innerHTML = '' 清空选项后,若没重置 selectedIndex,部分 Android 浏览器仍会保留旧选中态;还有,focus() 到子菜单项时,若没同步 aria-selected="true",VoiceOver 用户根本不知道当前高亮的是哪一项。



















