二级菜单最小可行结构是嵌套配合css :hover 或 :focus-within 控制显隐,子菜单需 position: absolute + display: none,父设 position: relative;移动端应优先用 :focus-within 替代 :hover,并补充 aria 属性与键盘导航支持。

HTML + CSS 实现二级菜单的最小可行结构
纯 HTML 无法“展开”菜单,必须配合 CSS(:hover 或 :focus-within)或 JS。最轻量、兼容性最好的方案是用嵌套 <ul></ul> + <li>,靠 CSS 控制子菜单显隐。
关键不是“怎么写标签”,而是“怎么让子菜单不撑开布局、不遮挡内容、鼠标移入才出现”。常见错误是直接写 display: block 导致默认显示,或没设 position: absolute 导致父容器高度被撑开。
- 一级菜单项用
<li>包裹<a></a>,子菜单用另一个<ul></ul>嵌在该<li>内 - 子菜单
<ul></ul>默认display: none,父<li>的:hover状态下设为display: block - 子菜单必须加
position: absolute,否则会破坏文档流,导致下方内容跳动 - 给父
<li>加position: relative,让子菜单的top/left相对于它定位
为什么 hover 在移动端失效?用 :focus-within 替代
手机没有“悬停”概念,:hover 在 iOS/Android 浏览器中只对可聚焦元素(如 <a></a>、<button></button>)触发一次,且无法保持展开状态。真实场景下,用户点开一级菜单后希望看到二级项,再点二级项跳转——这需要焦点逻辑。
:focus-within 是更现代、更可靠的方案:只要子元素获得焦点(比如点击了 <a></a>),父容器就生效。它兼容 Chrome 65+、Firefox 61+、Safari 15.4+;旧版 Safari 需回退到 JS。
立即学习“前端免费学习笔记(深入)”;
ISite企业建站系统是为懂点网站建设和HTML技术的人员(例如企业建站人员)而开发的一套专门用于企业建站的开源免费程序。本系统采用了全新的栏目维护模式,内容添加过程中,前后台菜单是一样的,需要维护前台某个栏目的内容,只需要进后台相应栏目即可,一般的企业人员只需要查看简易的说明就可以上手维护网站内容。通过自由度极高的模板系统,可以适应大多数情况的界面需求,后台带有标签生成器,建站只需要构架好HTM
- 把一级菜单的
<a></a>设为tabindex="0",确保可聚焦 - CSS 中用
li:focus-within > ul替代li:hover > ul - 避免用
onclick或addEventListener手动切class,除非要支持 IE11
display: none vs visibility: hidden 的实际影响
两者都让子菜单“看不见”,但行为完全不同:display: none 元素完全脱离渲染树,不占空间、不响应事件;visibility: hidden 仍占布局位置,只是透明,且子元素设 visibility: visible 会重新显示——这会导致二级菜单意外透出、点击穿透等问题。
做二级菜单时,必须用 display: none。用 visibility 的唯一合理场景是做淡入动画(配合 opacity),但此时得额外用 JS 控制 display 切换时机,否则动画开始前就已占位。
- 动画过渡只能作用于
opacity和transform,不能直接过渡display - 若要淡入效果,先设
opacity: 0; visibility: hidden; display: block,再用transition动opacity,最后在动画结束时设visibility: hidden(JS 监听transitionend) - 日常项目中,无动画需求就老实用
display: none/block,简单可靠
键盘导航和屏幕阅读器支持的关键配置
仅靠鼠标 hover 或点击,对键盘用户(Tab 键切换)和视障用户是不可用的。核心是让子菜单在父项获得焦点时可见,并允许用方向键操作。
原生语义最稳妥:用 <details><summary></summary></details> 可以零 JS 实现展开收起,但样式定制受限;若用 <nav> + <ul></ul></nav>,则必须补全 ARIA 属性和键盘逻辑。
- 一级菜单
<a></a>加aria-haspopup="true"和aria-expanded="false" - 子菜单
<ul></ul>加role="menu",每个子项<a></a>加role="menuitem" - 用 JS 监听
keydown,对ArrowDown/ArrowUp切换焦点,Escape关闭子菜单 - 不要忽略
aria-controls,它明确关联展开项与被控容器 ID
很多团队卡在“能点开就行”,但真正上线前,至少要保证 Tab 能进能出、Enter 能触发、Screen Reader 能读出“有子菜单”。这部分没做,等于把一类用户关在门外。









