用 nav 搭建导航栏
把菜单包裹在 <nav> 中,再把链接放进列表。Flexbox 会把列表变成一条横向的导航栏:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Navbar</title>
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
nav { background: #111827; }
nav ul { display: flex; gap: 4px; list-style: none; margin: 0; padding: 8px; }
nav a {
display: block; padding: 8px 14px; border-radius: 6px;
color: #e5e7eb; text-decoration: none;
}
nav a:hover { background: #374151; color: #fff; }
</style>
</head>
<body>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Courses</a></li>
<li><a href="#">Pricing</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</body>
</html>
list-style: none 和 padding: 0 去掉了项目符号和缩进。给每个链接设置 display: block,让它的内边距在导航栏中占据真实的空间,这样整个带内边距的区域都可以点击。删掉 display: flex 那一行,同一个菜单就会变成竖向列表,侧边栏菜单就是这个样子。
<nav> 本身没有任何外观。它的作用是表达含义:屏幕阅读器会把它作为导航地标呈现,用户按一个键就能跳到菜单,或者跳过它。
标记当前页面
给当前显示页面的链接加上 aria-current="page"。它会告诉屏幕阅读器“你在这里”,也给了 CSS 一个用来高亮链接的钩子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Current page</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
nav ul { display: flex; gap: 20px; list-style: none; padding: 0; margin: 0; border-bottom: 2px solid #e5e7eb; }
nav a { display: block; padding: 10px 0; color: #374151; text-decoration: none; }
nav a[aria-current="page"] {
color: #2563eb; font-weight: 600;
box-shadow: inset 0 -3px 0 #2563eb;
}
</style>
</head>
<body>
<nav aria-label="Main">
<ul>
<li><a href="#">Overview</a></li>
<li><a href="#" aria-current="page">Lessons</a></li>
<li><a href="#">Projects</a></li>
</ul>
</nav>
</body>
</html>
把 aria-current="page" 移到另一个链接上,高亮也会跟着移动。为这个属性设置样式,而不是用 .active 类,可以让外观和无障碍保持同步:有了其中一个,就一定有另一个。
一个页面上的多个 nav:标签与面包屑
一个页面可以有主菜单、面包屑和目录,每个都放在自己的 <nav> 中。给每个 nav 设置 aria-label,这样屏幕阅读器会读出“Breadcrumb 导航”,而不是三个一模一样的“导航”:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Breadcrumb</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
.crumbs ol { display: flex; flex-wrap: wrap; list-style: none; padding: 0; margin: 0; }
.crumbs li + li::before { content: "/"; margin: 0 8px; color: #9ca3af; }
.crumbs a { color: #2563eb; }
</style>
</head>
<body>
<nav class="crumbs" aria-label="Breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Docs</a></li>
<li><a href="#">HTML</a></li>
<li><span aria-current="page">nav</span></li>
</ol>
</nav>
<h1>HTML nav</h1>
</body>
</html>
面包屑使用有序列表 <ol>,因为顺序很重要。斜杠来自 CSS 的 ::before,所以屏幕阅读器不会在每一项之间读出“斜杠”。最后一项是当前页面,不是链接。
目录
指向同一页面各个部分的链接列表也是导航。#id 链接会跳转到带有该 id 的元素;在预览中试试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Table of contents</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
nav { border: 1px solid #e5e7eb; border-radius: 8px; padding: 8px 16px; }
nav h2 { font-size: 14px; margin: 4px 0; }
section { min-height: 200px; border-top: 1px solid #e5e7eb; }
</style>
</head>
<body>
<nav aria-labelledby="toc-title">
<h2 id="toc-title">On this page</h2>
<ol>
<li><a href="#install">Install</a></li>
<li><a href="#configure">Configure</a></li>
<li><a href="#deploy">Deploy</a></li>
</ol>
</nav>
<section id="install"><h2>Install</h2></section>
<section id="configure"><h2>Configure</h2></section>
<section id="deploy"><h2>Deploy</h2></section>
</body>
</html>
这里的标签来自一个可见的标题,通过 aria-labelledby 关联;当标题已经显示在屏幕上时,这比 aria-label 更好。
移动端菜单开关
在窄屏幕上,菜单通常隐藏在一个按钮后面。使用真正的 <button> 并加上 aria-expanded,链接仍然放在 <nav> 中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Menu toggle</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
button { font: inherit; padding: 6px 12px; border: 1px solid #111827; border-radius: 6px; background: #fff; cursor: pointer; }
#menu { list-style: none; padding: 0; margin: 8px 0 0; border: 1px solid #e5e7eb; border-radius: 8px; }
#menu a { display: block; padding: 10px 14px; color: #111827; text-decoration: none; }
#menu li + li { border-top: 1px solid #e5e7eb; }
</style>
</head>
<body>
<nav aria-label="Main">
<button aria-expanded="false" aria-controls="menu" id="toggle">Menu</button>
<ul id="menu" hidden>
<li><a href="#">Home</a></li>
<li><a href="#">Courses</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<script>
const toggle = document.getElementById('toggle');
const menu = document.getElementById('menu');
toggle.addEventListener('click', () => {
const open = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', String(!open));
menu.hidden = open;
});
</script>
</body>
</html>
aria-expanded 告诉屏幕阅读器菜单是否已展开,而 hidden 属性会在菜单收起时把列表从屏幕和无障碍树中一并移除。在宽屏幕上,你可以用媒体查询显示列表并隐藏按钮。
常见错误
- 把每一组链接都包进
<nav>。 它用于主菜单、面包屑、分页和目录。正文中的行内链接不是导航区块。
- 用
<div> 做菜单按钮。 它无法用键盘聚焦或按下。请使用 <button>。
- 多个 nav 都没有标签。 给每个 nav 加上
aria-label 或 aria-labelledby,以便区分。
- 只给当前链接换个颜色。 加上
aria-current="page";单靠颜色不会被读出来,也不是每个人都能看出来。
- 正式上线的页面中使用
href="#" 的链接。 在演示中没问题,但真正的菜单项需要真实的 URL,在新标签页中打开时才能正常工作。链接一页介绍了 href 的各种取值。