
导航条代码怎么写:从 HTML 结构到 CSS 样式
导航条是网站中帮助用户定位和切换内容的界面组件。它看起来只是几组文字链接,实际写好需要考虑语义、布局、屏幕尺寸、键盘操作和链接安全等问题。本文从一个简单、可运行的导航开始,逐步扩展到响应式菜单,并说明哪些场景适合用导航条、哪些内容不应塞进导航条。
一、先判断:什么内容适合放进导航条
导航条通常用于呈现网站或当前页面区域的主要导航链接,例如“首页”“产品”“文档”“关于我们”。它的核心作用是帮助用户前往其他页面或内容区域,而不是触发任意操作。
- 适合使用导航链接:站点主导航、文档目录、页脚导航、面包屑,以及单页应用中跳转到页面区块的目录。
- 不适合直接当作导航链接:提交表单、打开对话框、删除数据、切换一个开关等操作。这些操作通常应使用
<button>,而不是伪装成链接。 - 不宜塞入过多选项:主导航应突出高频、重要入口。大量低频链接可以放到页脚、侧边栏或单独的目录页。
导航条应当帮助用户建立页面结构,而不是承担所有交互。需要搜索、账户操作、购物车等功能时,可以把它们放在导航区域附近,但要根据实际含义选择表单、按钮或链接等元素。
二、用语义化 HTML 搭出导航结构
一组站点导航通常由 <nav>、无序列表 <ul>、列表项 <li> 和链接 <a> 构成。<nav> 表示重要的导航区域;列表则表达多个同级导航项之间的关系。
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/products/">产品</a></li>
<li><a href="/docs/">文档</a></li>
<li><a href="/about/">关于我们</a></li>
</ul>
</nav>
href 指定链接目标。站内路径可以使用相对路径或以斜杠开头的根路径;外部网站通常使用完整 URL。请把示例路径换成项目中实际存在的地址。不要为了让链接“看起来能点”而把所有目标写成 href="#":它会指向当前文档中的片段位置,可能造成页面跳动,也不能表达真实目的地。
一个页面可以有多个 <nav>,例如主导航和页脚导航。多个导航区域最好使用不同的可访问名称,方便屏幕阅读器用户区分。名称可以通过 aria-label 提供,也可以使用页面上已有的可见标题并通过 aria-labelledby 关联。不要为了让某个元素“更有语义”而给所有容器都加上导航角色。
如果页面有多个不同的导航区域,可以这样标注:
<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/docs/">文档</a></li>
</ul>
</nav>
<nav aria-label="页脚导航">
<ul>
<li><a href="/privacy/">隐私政策</a></li>
<li><a href="/terms/">服务条款</a></li>
</ul>
</nav>
三、用 CSS 实现横向导航条
下面的样式使用 Flexbox 将列表项排成一行。导航列表常见的默认外边距、内边距和项目符号由 CSS 重置;颜色、间距和圆角则根据设计需要调整。
<style>
* {
box-sizing: border-box;
}
.site-header {
background: #172554;
}
.site-nav {
max-width: 72rem;
margin-inline: auto;
padding: 0.75rem 1rem;
}
.site-nav ul {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
}
.site-nav a {
display: block;
padding: 0.65rem 0.85rem;
border-radius: 0.4rem;
color: #fff;
text-decoration: none;
}
.site-nav a:hover {
background: #263b78;
}
.site-nav a:focus-visible {
outline: 3px solid #fbbf24;
outline-offset: 3px;
}
.site-nav a[aria-current="page"] {
background: #fff;
color: #172554;
font-weight: 700;
}
</style>
<header class="site-header">
<nav class="site-nav" aria-label="主导航">
<ul>
<li><a href="/" aria-current="page">首页</a></li>
<li><a href="/products/">产品</a></li>
<li><a href="/docs/">文档</a></li>
<li><a href="/about/">关于我们</a></li>
</ul>
</nav>
</header>
把这段代码保存为 HTML 文件后即可在浏览器中查看。示例中的 CSS 写在 <style> 标签内,实际项目可以移入独立样式表。max-width 限制内容区域宽度,margin-inline: auto 在支持逻辑属性的现代浏览器中让它水平居中;如果项目需要支持较旧的浏览器,可以改用 margin-left: auto 和 margin-right: auto。
aria-current="page" 用于标记当前页面对应的链接。它不负责自动判断当前路由,也不会自动添加视觉样式;需要由页面模板或应用逻辑将属性放在正确的链接上,再通过 CSS 呈现当前状态。一个页面通常只应有一个代表当前页面的主导航链接。
四、响应式布局:先让内容能换行
桌面端的一行导航在窄屏上可能放不下。最简单的做法是允许导航项换行,让布局适应可用空间。它适合链接数量不多、换行后仍清晰的导航,也不需要额外 JavaScript。
.site-nav ul {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}
.site-nav a {
display: block;
padding: 0.65rem 0.75rem;
}
如果移动端需要节省空间,可以把导航折叠到菜单按钮后。折叠菜单需要有真实的开关行为,并让按钮状态能够被辅助技术识别。下面的完整示例使用原生按钮和少量 JavaScript:窄屏初始收起菜单,点击后展开;宽屏始终显示链接。复制为独立 HTML 文件即可运行。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>响应式导航示例</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
}
.header {
background: #172554;
color: #fff;
}
.nav-wrap {
max-width: 72rem;
margin-inline: auto;
padding: 0.75rem 1rem;
}
.nav-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.brand {
color: inherit;
font-weight: 700;
text-decoration: none;
}
.menu-toggle {
padding: 0.55rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 0.4rem;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
}
.menu-toggle:focus-visible,
.nav-links a:focus-visible {
outline: 3px solid #fbbf24;
outline-offset: 3px;
}
.nav-links {
display: none;
margin: 0.75rem 0 0;
padding: 0;
list-style: none;
}
.nav-links.is-open {
display: block;
}
.nav-links a {
display: block;
padding: 0.7rem 0.5rem;
border-radius: 0.35rem;
color: inherit;
text-decoration: none;
}
.nav-links a:hover {
background: #263b78;
}
@media (min-width: 48rem) {
.nav-top {
justify-content: flex-start;
}
.menu-toggle {
display: none;
}
.nav-links,
.nav-links.is-open {
display: flex;
gap: 0.5rem;
margin: 0 0 0 auto;
}
.nav-links a {
padding: 0.65rem 0.8rem;
}
}
</style>
</head>
<body>
<header class="header">
<nav class="nav-wrap" aria-label="主导航">
<div class="nav-top">
<a class="brand" href="/">示例站点</a>
<button
class="menu-toggle"
type="button"
aria-expanded="false"
aria-controls="primary-links">
菜单
</button>
</div>
<ul class="nav-links" id="primary-links">
<li><a href="/" aria-current="page">首页</a></li>
<li><a href="/products/">产品</a></li>
<li><a href="/docs/">文档</a></li>
<li><a href="/about/">关于我们</a></li>
</ul>
</nav>
</header>
<main>
<h1>页面内容</h1>
<p>缩窄浏览器窗口,测试移动端菜单。</p>
</main>
<script>
const button = document.querySelector(".menu-toggle");
const links = document.querySelector("#primary-links");
button.addEventListener("click", () => {
const isOpen = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!isOpen));
links.classList.toggle("is-open", !isOpen);
});
</script>
</body>
</html>
这里的 aria-expanded 表示按钮控制的内容当前是否展开,aria-controls 指向受控列表的 ID。按钮是原生 <button>,因此可用键盘聚焦,也支持 Enter 和空格触发;点击逻辑同步更新可见状态和展开状态。不要只用 CSS 隐藏菜单,却让属性仍显示为已展开,反之亦然。
示例在宽屏断点处隐藏菜单按钮并直接显示链接。断点 48rem 不是通用规定,应根据导航内容、字体大小和实际布局测试后选择。若链接文本较长,可能需要更早切换为折叠布局,或改用换行、缩短标签等方案。断点值也不限制用户缩放页面;布局应在放大和窄视口下保持可用。
五、当前页、跳转目标与链接属性
普通站内导航使用真实地址,例如 href="/docs/"。单页内跳转可以使用页面中确实存在的 ID:
<nav aria-label="本页目录">
<ul>
<li><a href="#install">安装</a></li>
<li><a href="#usage">使用方法</a></li>
</ul>
</nav>
<section id="install">
<h2>安装</h2>
<p>安装内容……</p>
</section>
<section id="usage">
<h2>使用方法</h2>
<p>使用内容……</p>
</section>
片段链接依赖目标元素的 ID。ID 在同一文档中应唯一;如果目标不存在,链接就无法到达预期内容。需要固定标题被顶部栏遮挡时,可以考虑为目标区域设置 scroll-margin-top,而不是靠插入空白元素制造偏移。
通常不需要给普通站内链接添加 target="_blank"。若确实需要在新标签页打开外部链接,可以明确告知用户,并使用:
<a href="https://example.com/" target="_blank" rel="noopener">
在新标签页打开示例网站
</a>
现代浏览器对带有 target="_blank" 的链接通常会隐式提供类似 noopener 的隔离行为;显式写出 rel="noopener" 仍有助于清晰表达意图和兼容较旧环境。noreferrer 还会影响来源信息(Referer)的发送,不应为了“更安全”而不加判断地使用。若目标 URL 来自用户输入或外部数据,应按业务规则校验允许的协议和域名,不能把未经校验的字符串直接拼入链接。
六、下拉菜单与复杂导航的边界
如果导航项需要展开子链接,先判断是否真的需要下拉菜单。只有少量子项时,直接展示、使用独立目录页或在移动端分层呈现,可能更易理解。桌面端仅靠悬停打开的菜单对触屏、键盘和辅助技术用户不友好;还要处理焦点进入、退出、关闭方式和菜单状态。
导航菜单不是应用程序菜单。站点导航通常应保留普通链接的浏览器行为,不应随意给链接添加 role="menuitem" 或把容器标为 role="menu"。这些 ARIA 角色对应更复杂的交互模式,会带来键盘方向键管理等额外责任。对常见网站导航,使用 <nav>、列表、链接和按钮通常更合适。
若使用下拉交互,应确保触屏可以打开、键盘可以到达和关闭、焦点不会被隐藏内容截断,且展开状态有可感知的提示。不要只通过颜色区分展开状态,也不要仅在鼠标悬停时显示重要链接。复杂菜单建议基于实际设备和辅助技术进行测试。
七、常见错误及改进方式
- 用空链接占位:
href="#"不是“暂未完成”的安全替代品。没有目标时,先不要渲染为链接;若需要触发操作,使用按钮。 - 把按钮写成链接:链接用于导航,按钮用于执行操作。两者的键盘行为、语义和默认行为不同,应该按功能选择。
- 只用颜色表示当前页面:保留文本标签,并使用
aria-current="page"等语义标记;视觉上也可结合底色、边框或字重区分。 - 删除焦点轮廓:
outline: none会让键盘用户难以知道焦点位置。若替换默认样式,应提供清晰、足够醒目的:focus-visible状态。 - 给所有导航项写固定宽度:文字变长、语言切换或用户缩放时容易溢出。优先使用 Flexbox、间距、换行和合适的断点。
- 使用过小的点击区域:文字链接可以有较大的上下左右内边距,避免只让字形本身可点击。尺寸要求应结合目标设备和适用规范核对,不存在一个对所有产品、法规和场景都通用的固定值。
- 移动端菜单状态不同步:菜单视觉上打开但
aria-expanded仍为false,会造成状态信息不一致。交互逻辑应统一更新显示和状态属性。 - 把导航条固定在视口顶部却不留空间:固定定位元素可能遮挡正文和片段跳转目标。应评估内容偏移、目标滚动位置、窄屏高度以及缩放后的表现。
- 在每个页面复制出不一致的导航:改名、改链接时容易漏改。使用模板、组件或服务端公共布局集中维护,并由当前页面数据生成活动状态。
八、可访问性与安全检查清单
发布前可以逐项检查:
- 导航区域是否用
<nav>表达,多个导航是否有能区分的名称? - 每个链接是否指向真实、可访问的目标?链接文字是否能说明目的,而不是只写“点击这里”?
- 当前页面是否通过
aria-current="page"标出,且样式不只依赖颜色? - 只用键盘是否能依次访问链接、触发移动菜单,并看清焦点位置?
- 窄屏、放大文字和长标签下,内容是否仍能阅读和操作?
- 外部新窗口是否确有必要,是否清楚提示用户?动态 URL 是否经过允许协议和目标范围校验?
HTML 转义和 URL 校验解决的是不同问题。写在 HTML 文档中的示例代码要正确转义尖括号;将用户提供的文本插入页面时,应使用安全的 DOM 文本接口或可靠的模板自动转义机制,避免把不可信字符串当作 HTML 解析。动态链接还需要根据应用规则限制目标协议与域名。仅靠 CSS 或把文字写进导航条,并不能构成安全校验。
九、按场景选择实现方式
内容少、导航项能排成一行时,用语义化列表加 Flexbox 即可;窄屏下换行仍清晰时,开启 flex-wrap 是维护成本最低的方案;确实需要节省空间时,再加入按钮折叠和状态同步逻辑。页面内目录使用指向真实 ID 的片段链接;有大量层级内容时,优先考虑信息架构和独立页面,不要把所有链接都塞进多层悬停菜单。
写导航条时,先确定链接要去哪里,再选 HTML 元素和交互方式,最后用 CSS 调整布局。只要语义明确、目标真实、键盘可用、窄屏可读,导航条就不必复杂。对于需要下拉、固定定位或根据路由动态高亮的场景,再逐项增加功能,并针对真实页面和设备验证行为。




