ul和li是导航菜单唯一可靠的语义基础,必须用nav包裹并遵循严格嵌套规则:子菜单必须为li的直接子ul,缩进仅用padding-left逐级累加,显隐用checkbox兄弟选择器实现。

ul 和 li 不是用来“高级用法”的——它们是导航菜单唯一可靠的语义基础。任何绕开它的方案(比如用 div + span 模拟)都会在可访问性、SEO、键盘导航或屏幕阅读器支持上出问题。
为什么必须用 nav 包裹 ul,而不是直接写 ul
浏览器遇到裸 ul 会自动补全为 <ul></ul>,但语义丢失:nav 是唯一被所有读屏软件一致识别为“导航区域”的标签。没它,aria-label="主导航" 就没容器可挂;搜索引擎也难区分这是菜单还是普通列表。
- 错误写法:
<ul class="menu">...</ul>(无nav) - 正确写法:
<nav aria-label="主导航"><ul>...</ul></nav> - 别用
menu标签:Chrome 当作ul渲染,Firefox 可能忽略,W3C 明确不鼓励用于导航
li 里嵌套子菜单时,ul 必须是直接子元素
二级菜单不是加个 div 或 span 再塞 a 就行——它必须是 li 的直接子 ul,否则 CSS 的 :hover 或 :checked ~ ul 选择器会失效,且破坏 ARIA 层级关系。
- 合法结构:
<li>@#@#@#@#@#@#@#@#@#@0<ul class="submenu"><li><a>账户</a></li></ul> </li> - 非法结构:
<li> <a>设置</a><div><ul>...</ul></div> </li>(ul被div隔开,~选择器断掉) - 子菜单
ul不能设position: absolute后再给父li加position: relative——这会让焦点框错位、屏幕阅读器丢失父子关系
缩进和层级视觉表达,只能靠 padding-left
缩进不是为了“看起来整齐”,而是向辅助技术传达嵌套深度。用 margin-left 会把整个 li 推离文档流,导致 Tab 键顺序错乱;text-indent 只缩第一行文字,对图标、复选框等无效;transform: translateX() 更危险——:focus-visible 框会偏移,无障碍 API 报告的位置信息失真。
- 一级菜单:
padding-left: 1rem - 二级菜单:
padding-left: 2.5rem(不是固定 1.5rem) - 三级菜单:
padding-left: 4rem(逐级累加,不可复用同一值) - 建议加
border-left: 2px solid #e0e0e0,比纯缩进更易识别层级
纯 CSS 控制展开收起,input[type="checkbox"] 必须和 ul.submenu 是兄弟关系
不用 JS 实现二级菜单显隐,核心是 :checked ~ .submenu。这个选择器要求:input 和 ul 必须同级、紧邻,中间不能插 label 或其他块级元素。
- 有效结构:
<input type="checkbox" id="s1"><label for="s1">设置</label><ul class="submenu">...</ul> - 失效结构:
<input ...><label><span>设置</span></label><ul>...</ul>(span插入后,~不再匹配) - iOS Safari 15.4 以下有 focus 失效问题,若需兼容老设备,得 fallback 到 JS
- 别用
:focus-within替代——它在菜单收起后仍可能保持展开态,逻辑难控制
最常被忽略的点:所有 a 必须带 href 属性(哪怕暂时是 href="#"),否则键盘 Tab 无法聚焦;hover 效果必须加在 a 上,不是 li ——否则鼠标移到文字外但仍在 li 内时会误触发。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











