子选择器>可精确控制多级菜单样式,避免层级污染;属性选择器能区分链接类型减少JS干预;:has()可实现父级响应子状态但兼容性有限;需分离:hover与:focus以保障键盘用户访问。

用子选择器 > 精确控制多级菜单样式范围
嵌套层级一深,a、li、ul 就容易互相污染。比如给一级菜单加背景色,结果所有子菜单的 li 都被影响——这不是你想要的,是选择器太宽泛了。
直接用子选择器限定作用域最稳:
nav.main-nav > ul > li { display: inline-block; }
nav.main-nav > ul > li > ul { display: none; }
nav.main-nav > ul > li:hover > ul { display: block; }
注意三点:
-
>只匹配**直接子元素**,不会穿透到第三层 - 写全路径(如
nav.main-nav > ul > li)比只写li更安全,避免意外命中其他导航区域 - 移动端需配合
@media重置该规则,否则 hover 在触摸设备上无效
用属性选择器区分链接类型,减少 JS 干预
很多团队为“外部链接”“当前页”“禁用项”单独加 class,其实 CSS 本身就能识别。
立即学习“前端免费学习笔记(深入)”;
常见写法:
-
a[href^="http"]:匹配以http开头的绝对链接,可加图标或新窗口提示 -
a[href="/about"]或a[href$="/about/"]:用精确路径或结尾匹配当前页(注意 trailing slash 一致性) -
a[aria-current="page"]:比.active更语义化,也更利于屏幕阅读器 -
a[disabled]不生效(a不支持disabled属性),改用a[aria-disabled="true"]+pointer-events: none
这样写,后端渲染或静态生成时无需额外标记 class,样式与状态解耦。
用 :has() 实现“父级响应子状态”,但注意兼容性
想让一级 li 在子菜单展开时加粗或变色?过去只能靠 JS 添加 class,现在可以用:
nav.main-nav > ul > li:has(> ul:is(:hover, :focus-within)) { font-weight: 600; }
但必须清楚::has() 在 Safari 15.4+、Chrome 105+、Firefox 121+ 才稳定支持;旧版 Edge 和部分安卓 WebView 完全不认。生产环境若需兼容 IE 或低版本 Android,得回退到 JS 监听或用 :hover 模拟(仅限桌面)。
另外,:has() 不能出现在选择器开头(如 :has(li) a 无效),且内部不能含伪元素或某些伪类(如 :nth-child)。
避免 :hover 和 :focus 冲突导致键盘用户失焦
很多菜单写成 a:hover, a:focus 一起设 outline,看似省事,但问题明显:键盘用户 tab 进入后,再用鼠标悬停,焦点框会消失——因为 :hover 覆盖了 :focus 样式。
正确做法是分开定义,并确保 :focus 优先级不被覆盖:
- 把
:focus放在:hover后面(LVHA 原则) - 用
outline: 2px solid #007acc明确指定,别依赖outline: auto - 移动端加
@media (hover: none) and (pointer: coarse)关闭:hover,只留:focus和:active
真正难处理的是触屏+键盘混合场景(比如 iPad 外接键盘),这时 :focus-within 比单纯 :focus 更可靠。


















