导航条必须用<nav>包裹且内部用<ul><li>结构,因<nav>提供语义标识,配合嵌套<ul><li>确保屏幕阅读器识别导航意图、搜索引擎理解链接用途,并支持可访问性交互;错误结构会导致读屏误读、Lighthouse报错及焦点混乱。

导航条区域必须用 <nav> 包裹,内部必须用 <ul> 和 <li> 构建结构 —— 这不是“推荐做法”,而是语义正确性和可访问性的硬性要求。
为什么不能直接用 div + a 做导航
屏幕阅读器会把 <div><a>首页</a><a>产品</a></div> 识别为普通文本容器,无法告知用户“这是导航区”;搜索引擎也难以判断链接的导航意图。而 <nav> 是 HTML5 明确规定的导航语义容器,配合 <ul> 表示“一组并列的导航项”,<li> 表示“单个导航入口”,逻辑清晰、机器可读。
常见错误现象:
– 页面被读屏软件读作“一段文字:首页 产品 关于 我们”
– Lighthouse 可访问性审计报错 “Navigation region not identified with <nav>”
– 多级菜单展开后焦点顺序混乱(因缺乏嵌套列表结构)
UL 和 LI 的结构必须严格嵌套
<nav> 内部只能有一层 <ul> 作为主菜单容器,每个导航项必须是 <li>,且 <a> 必须直接包在 <li> 里 —— 不能写成 <li><span><a>首页</a></span></li>,否则会破坏列表项语义。
多级菜单时,子菜单必须是嵌套在对应 <li> 内的另一个 <ul>:
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li>
<a href="/products">产品</a>
<ul>
<li><a href="/products/web">Web 工具</a></li>
<li><a href="/products/cli">命令行</a></li>
</ul>
</li>
</ul>
</nav>关键点:
– 子 <ul> 必须在父 <li> 内,不能放在外面
– 每个 <a> 都应有明确 href,空链接(href="#")需配合 JavaScript 并添加 aria-haspopup 和 aria-expanded
– 当前页面链接建议加 aria-current="page",例如 <a href="/products" aria-current="page">产品</a>
CSS 排版时 display: inline-block 和 flex 的取舍
老式写法用 li { display: inline-block; } 或 li { float: left; } 实现横排,但存在基线对齐、空白符换行、清除浮动等隐性问题。现代项目应优先用 display: flex:
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 1rem; /* 比 margin 更可控,不需处理最后一个元素 */
}
<p>nav ul li a {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
}注意:
– 不要给 <li> 设固定宽度,它应由内容撑开
– gap 在 Flex 中比 margin-right 更安全,避免最后一项多出空白
– 若需兼容 IE11,仍得回退到 inline-block,但必须设 font-size: 0 在父 <ul> 上消除空白符间隙
容易被忽略的可访问性细节
很多开发者写了语义结构,却漏掉几个关键属性,导致导航在键盘或读屏下不可用:
– <nav> 必须带 aria-label,如 <nav aria-label="主导航">;若页面有多个 <nav>(比如页脚也有),必须区分 label
– 子菜单触发按钮(如“产品”文字链接)需加 aria-haspopup="menu" 和 aria-expanded="false",并在 JS 切换时同步更新 aria-expanded
– 键盘操作必须支持 Tab 进入、→← 切换、Enter 展开、Esc 关闭、↓ 进入子菜单 —— 这些不是 CSS 能解决的,必须配合 JS 实现焦点管理
– <ul> 上不要加 role="navigation",因为 <nav> 已自带该 role,重复会导致读屏重复播报


















