多级菜单HTML结构必须用<ul><li>严格嵌套,子<ul>须为父<li>直接子元素;外层<ul>加role="menubar",子<ul>加role="menu",菜单项加role="menuitem"及aria-haspopup/expanded;禁用平铺<ul>、<ol>、<nav>包裹或直挂<ul>;移动端推荐checkbox+label+~选择器方案。

多级菜单的 HTML 结构必须用 <ul> 和 <li> 嵌套,子菜单 <ul> 必须作为父级 <li> 的**直接子元素**,不能外挂、不能用 <div> 替代,否则键盘导航会跳层、屏幕阅读器读不出层级、CSS 选择器也会失效。
结构必须严格嵌套
每个可展开的菜单项(比如“服务”“产品”)本身是 <li>,它的子菜单必须是一个 <ul>,且这个 <ul> 要紧贴写在该 <li> 内部,中间不能有其他标签或换行干扰:
- ✅ 正确:
<li>服务<ul><li>咨询</li><li>实施</li></ul></li> - ❌ 错误:
<li>服务</li><ul><li>咨询</li></ul>(子<ul>在父<li>外) - ❌ 错误:
<li><div>服务</div><ul>...</ul></li>(<div>插入破坏语义和 DOM 关系)
语义角色与 ARIA 属性不能少
仅靠嵌套还不够,要让菜单对键盘用户和读屏软件可用,必须补全语义属性:
- 最外层
<ul>加role="menubar" - 每层子
<ul>加role="menu" - 每个可点击的菜单项(
<a>或<button>)加role="menuitem" - 带子菜单的项额外加
aria-haspopup="true"和aria-expanded="false"(JS 控制展开时同步更新aria-expanded)
避免常见结构陷阱
这些写法看着差不多,但实际会导致菜单不可用:
立即学习“前端免费学习笔记(深入)”;
- 把所有
<ul>并列平铺(如一级、二级、三级<ul>全在同级),CSS 就无法精准定位“当前项的子菜单” - 用
<ol>或<nav>直接包裹子菜单——它们不是列表容器,不支持嵌套语义 -
<a>标签直接作为<ul>子元素(没包在<li>里),会破坏焦点流和无障碍遍历顺序
移动端和可访问性要点
纯 :hover 在手机上无效,所以结构设计要兼容点击/聚焦逻辑:
- 推荐用隐藏
<input type="checkbox">+<label>+~ .submenu的纯 CSS 方案,状态可记忆、无需 JS、触摸友好 - 确保
<label>和对应<ul class="submenu">是**紧邻的兄弟节点**,中间不能有空格或换行(HTML 空白符会被解析为文本节点,破坏~选择器) - 小屏下用
@media (max-width: 768px)强制所有子菜单display: none,别只靠 JS 切换或 visibility 隐藏



















