最稳妥的导航结构是用 <nav> 包裹 <ul><li><a> 嵌套,<li> 不可省略,href 须为有效 URL;横排布局优先用 display: flex;激活项用 aria-current="page";下拉菜单需 role="menu" 及键盘支持。

用 <nav> 包裹 <ul> 是最稳妥的写法
语义正确、无障碍支持好、主流框架和 CSS 重置都默认适配这种结构。别直接把 <a> 塞进 <nav> 里,也不要用 <div> 模拟列表——屏幕阅读器可能读不出导航项数量,SEO 也弱。
常见错误现象:Navigation menu is not announced as a list(在 VoiceOver 或 NVDA 中只读成“导航”,不报“共 5 项”)。
-
<nav>内部必须用<ul><li><a>嵌套,不能省略<li> - 每个
<a>的href值不能是#或空字符串(否则键盘 Tab 会卡住,且被部分爬虫忽略) - 如果只有一个导航区域,不必加
aria-label;多个时(如页头+页脚),要补上aria-label="主菜单"或aria-label="页脚导航"
display: flex 是横排导航最可控的布局方式
用 float 或 inline-block 容易出现换行错位、垂直对齐不一致、间隙难消除等问题;flex 能天然解决对齐、等宽、响应式收缩等需求。
性能影响小,所有现代浏览器(包括 IE11)都支持 display: flex,但 IE11 对 flex-wrap 和 gap 支持有限,需用 margin 模拟间距。
立即学习“前端免费学习笔记(深入)”;
- 给
<ul>设display: flex,不是给<nav> -
<li>默认不要设宽,让内容撑开;需要等宽时用flex: 1,但注意文字过长会溢出,得配合white-space: nowrap; overflow: hidden; text-overflow: ellipsis - 横向间距统一用
gap: 1rem(Chrome/Firefox/Edge 支持良好),IE11 则改用margin-right并给最后一个<li>清除
当前激活项用 aria-current="page" 比 class="active" 更可靠
CSS 类名 active 只对视觉有效,对辅助技术无意义;aria-current="page" 会被屏幕阅读器明确读作“当前页面”,且不影响样式控制(照样可以写 [aria-current="page"] { font-weight: bold; })。
使用场景:服务端渲染或静态站点中,由后端/构建工具注入该属性;前端路由(如 React Router)推荐用 NavLink 组件自动处理,而不是手动判断 location.pathname。
- 只对指向当前 URL 的
<a>添加,不要加在<li>上 - 值必须是
"page"(不是"true"或"current"),其他合法值还有"step"(向导)、"location"(地理定位) - 别同时用
aria-current和aria-selected—— 后者用于可切换的 tab,语义冲突
下拉菜单必须用 <ul> 嵌套,且要有 role="menu" 和键盘支持
纯 CSS hover 下拉在触屏设备上失效,且键盘用户无法进入子菜单;没加 role 属性时,屏幕阅读器可能把整个下拉块当成普通列表,不提示“菜单已展开”。
容易踩的坑:role="menu" 不等于右键上下文菜单,它专指水平主菜单下的纵向子项,且必须配合 role="menuitem" 和方向键交互逻辑。
- 子
<ul>要放在父<li>内,不能脱离结构;外层<ul>保持role="menubar",子级设role="menu" - 每个子
<a>加role="menuitem",并绑定Enter/Space点击、ArrowDown进入、Escape关闭等事件(可复用 WAI-ARIA Authoring Practices 的参考实现) - 不要用
visibility: hidden控制显隐——它仍可聚焦;应该用display: none或aria-hidden="true"+inert(兼容性差时用 JS 切换tabindex="-1")
aria-current 的一致性——比如点击跳转后没同步更新状态,或者子菜单展开时焦点没移到第一个菜单项。这些细节不处理,导航栏就只是“看起来能用”。



















