纯CSS下拉菜单应以:hover控制二级菜单显隐,结构用嵌套ul/li,二级菜单用position: absolute定位;移动端需checkbox模拟开关;键盘可访问性须JS补足aria-expanded等逻辑。

用 :hover 触发二级菜单显示,别用 JS 绑定点击
纯 CSS 下拉菜单的核心是利用父元素 :hover 控制子菜单的 display 或 visibility。JS 不仅增加复杂度,还会破坏键盘导航(比如 Tab 键无法进入子项)和移动端兼容性(iOS Safari 对 :hover 有特殊处理,但下拉菜单本身在触屏设备上本就该降级为点击展开,这点后面会提)。
常见错误是给 加 onclick 或监听 mouseenter,结果导致移动端点一次没反应、点两次才展开,或者焦点管理混乱。
实操建议:
- 结构必须是嵌套的
,外层是一级项,内层是二级菜单 - 初始状态设
ul ul { display: none; },再用li:hover > ul { display: block; }触发 - 避免用
visibility: hidden+opacity: 0做“隐藏”,它仍占文档流且可被屏幕阅读器读取,语义错误
position: absolute 是二级菜单定位的唯一可靠方式
如果二级菜单用 float、inline-block 或 flex 撑开父容器,会导致布局抖动、换行、宽度失控——尤其当一级菜单文字长度不一时。
正确做法是让二级菜单脱离文档流,靠父 定位:
- 父
必须设position: relative - 二级
设position: absolute; top: 100%; left: 0;,这样它始终紧贴一级项下方 - 若需右对齐(比如最后一项靠右展开),改用
right: 0而非left: auto,后者不被所有浏览器支持 - 务必加
min-width: 100%,否则二级菜单宽度可能被内容压缩,文字折行
移动端适配不是“加个媒体查询”就完事
纯 CSS 下拉菜单在手机上天然失效:触屏没有稳定的 :hover,用户点一下是“激活”,再点一下才是“跳转”,中间没有悬停态。硬撑 :hover 会导致点击穿透或误操作。
真实可行的做法是用媒体查询 + checkbox 模拟开关(无 JS):
- 在一级
内放一个隐藏和对应包住文字 - 二级
改为input:checked ~ ul { display: block; } - 桌面端用
@media (min-width: 768px)恢复:hover行为,同时隐藏 checkbox 和 label - 注意:iOS 上
label点击区域太小会导致点不中,需加padding或设min-height
键盘可访问性(Tab/Enter/Escape)没法绕过 JS,但可以最小化依赖
WCAG 要求下拉菜单支持键盘操作:Tab 进入一级项,Enter 展开二级,Escape 收起,方向键切换焦点。纯 CSS 做不到这点——:focus-within 可以部分替代 :hover,但无法响应 Escape 或控制焦点流。
如果项目必须达标(比如政府、银行类网站),最低成本方案是只用 JS 处理键盘逻辑,样式仍由 CSS 控制:
- 保留全部 CSS 的
:hover和position规则 - JS 只监听
keydown,在Enter时给当前加aria-expanded="true",并手动focus()到第一个二级项 - 不重写显示逻辑,仍靠
.menu-item[aria-expanded="true"] > ul { display: block; }控制 - 这样 CSS 仍是主体,JS 只补足无障碍缺口,维护成本低,也避免了“CSS 写一半、JS 补一半”的割裂
真正难的不是写出能展开的菜单,而是让它在小屏上不卡顿、在键盘下不跳失、在屏幕阅读器里读得清层级——这些细节堆起来,比实现本身花时间得多。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











