语义化与无障碍(Semantic HTML & Accessibility)
目录
- 语义标签:结构语义元素
- 表意标签:内容语义元素
- 无障碍基础:ARIA 属性
- 可聚焦性与键盘导航
- 屏幕阅读器友好实践
- 常见误区与最佳实践
语义标签:结构语义元素
<header> - 页头/区块头部
用途: 表示页面或区块的头部,通常包含标题、logo、导航等。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <header> <h1>网站标题</h1> <nav>导航菜单</nav> </header>
<article> <header> <h2>文章标题</h2> <p>作者:张三 | 发布时间:2026-01-05</p> </header> <p>文章内容...</p> </article>
|
注意事项:
- 一个页面可以有多个
<header>
- 通常包含
<h1>-<h6> 标题元素
- 不要与
<head> 混淆(<head> 是文档元数据)
<nav> - 导航区域
用途: 表示页面的主要导航链接集合。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <nav aria-label="主导航"> <ul> <li><a href="/">首页</a></li> <li><a href="/about">关于</a></li> <li><a href="/contact">联系</a></li> </ul> </nav>
<nav aria-label="面包屑导航"> <ol> <li><a href="/">首页</a></li> <li><a href="/products">产品</a></li> <li aria-current="page">当前页面</li> </ol> </nav>
|
注意事项:
- 不是所有链接都需要放在
<nav> 中
- 通常只用于主要的导航区域
- 建议添加
aria-label 说明导航用途
<main> - 主要内容区域
用途: 表示页面的主要内容,每个页面应该只有一个。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <body> <header>...</header> <nav>...</nav> <main> <h1>页面主标题</h1> <article>主要内容...</article> </main> <aside>侧边栏</aside> <footer>...</footer> </body>
|
注意事项:
- 每个页面只能有一个
<main>
- 不应该嵌套在其他语义元素中(如
<article>、<aside>、<nav>、<header>、<footer>)
- 屏幕阅读器可以通过快捷键直接跳转到
<main> 区域
<article> - 独立文章/内容块
用途: 表示独立、完整的内容单元,可以独立分发或重用。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <article> <header> <h2>文章标题</h2> <p>作者:<address>张三</address> | <time datetime="2026-01-05">2026年1月5日</time></p> </header> <p>文章内容...</p> <footer> <p>标签:HTML, CSS</p> </footer> </article>
<article> <h3>评论标题</h3> <p>评论内容...</p> </article>
|
注意事项:
<article> 可以嵌套(如文章中的评论)
- 应该包含标题(
<h1>-<h6>)
- 内容应该是自包含的、可独立理解的
<section> - 文档章节/区块
用途: 表示文档的通用章节,通常有标题。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <article> <h1>文章标题</h1> <section> <h2>第一章</h2> <p>第一章内容...</p> </section> <section> <h2>第二章</h2> <p>第二章内容...</p> </section> </article>
<section aria-labelledby="features-heading"> <h2 id="features-heading">产品特性</h2> <ul> <li>特性1</li> <li>特性2</li> </ul> </section>
|
注意事项:
<section> 和 <article> 的区别:
<article>:独立、完整的内容单元
<section>:文档的通用章节或分组
- 通常应该包含标题
- 如果只是用于样式化,考虑使用
<div>
<aside> - 侧边栏/附加内容
用途: 表示与主要内容相关但可以独立存在的内容。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <main> <article> <h1>文章标题</h1> <p>文章内容...</p> </article> <aside> <h2>相关文章</h2> <ul> <li><a href="#">相关文章1</a></li> <li><a href="#">相关文章2</a></li> </ul> </aside> </main>
<aside> <blockquote> <p>这是一段引用内容</p> <cite>— 引用来源</cite> </blockquote> </aside>
|
注意事项:
- 内容应该与主要内容相关
- 可以放在
<main> 内部或外部
- 通常用于侧边栏、相关链接等
<footer> - 页脚/区块尾部
用途: 表示页面或区块的尾部,通常包含版权信息、联系方式等。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <footer> <p>© 2026 公司名称</p> <address> 联系地址:<br> 邮箱:<a href="mailto:contact@example.com">contact@example.com</a> </address> </footer>
<article> <h1>文章标题</h1> <p>文章内容...</p> <footer> <p>标签:HTML, CSS</p> <p>最后更新:<time datetime="2026-01-05">2026-01-05</time></p> </footer> </article>
|
注意事项:
- 一个页面可以有多个
<footer>
- 通常包含版权信息、联系方式、相关链接等
- 不要与页脚样式混淆,语义化与样式无关
表意标签:内容语义元素
<figure> 和 <figcaption> - 图表与说明
用途: <figure> 表示独立的图表、图片、代码块等,<figcaption> 提供说明文字。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <figure> <img src="photo.jpg" alt="美丽的风景"> <figcaption>这是一张美丽的风景照片</figcaption> </figure>
<figure> <pre><code> function hello() { console.log('Hello, World!'); } </code></pre> <figcaption>JavaScript 示例代码</figcaption> </figure>
<figure> <img src="chart1.png" alt="图表1"> <img src="chart2.png" alt="图表2"> <figcaption>两个相关图表的对比</figcaption> </figure>
|
注意事项:
<figcaption> 必须是 <figure> 的第一个或最后一个子元素
- 如果图片已经有足够的
alt 文本,<figcaption> 可以提供额外信息
- 屏幕阅读器会读取
<figcaption> 的内容
<time> - 时间日期
用途: 表示时间或日期,提供机器可读的格式。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <time datetime="2026-01-05">2026年1月5日</time>
<time datetime="2026-01-05T14:30:00+08:00">2026年1月5日下午2:30</time>
<time datetime="2026-01-05">今天</time> <time datetime="2026-01-04">昨天</time>
<time datetime="2026-01-05" title="2026年1月5日">3天前</time>
|
datetime 属性格式:
- 日期:
YYYY-MM-DD(如 2026-01-05)
- 日期时间:
YYYY-MM-DDTHH:mm:ss(如 2026-01-05T14:30:00)
- 带时区:
YYYY-MM-DDTHH:mm:ss+HH:mm(如 2026-01-05T14:30:00+08:00)
- 仅时间:
HH:mm:ss(如 14:30:00)
注意事项:
datetime 属性提供机器可读的格式
- 元素内容可以是人类可读的格式
- 有助于搜索引擎和日历应用理解时间信息
<address> - 联系信息
用途: 表示文档作者或组织的联系信息。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <article> <h1>文章标题</h1> <p>文章内容...</p> <address> 作者:<a href="mailto:author@example.com">张三</a><br> 网站:<a href="https://example.com">example.com</a> </address> </article>
<footer> <address> 公司名称<br> 地址:北京市朝阳区xxx街道<br> 电话:<a href="tel:+8613800000000">138-0000-0000</a><br> 邮箱:<a href="mailto:contact@example.com">contact@example.com</a> </address> </footer>
|
注意事项:
- 不应该用于任意地址(如邮政地址),只用于联系信息
- 通常包含邮箱、电话、网站链接等
- 可以嵌套在
<article>、<footer> 等元素中
<abbr> - 缩写词
用途: 表示缩写或首字母缩略词,提供完整形式。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <p>我们使用 <abbr title="HyperText Markup Language">HTML</abbr> 构建网页。</p>
<p><abbr title="World Wide Web">WWW</abbr> 是互联网的重要组成部分。</p>
<p>速度:100 <abbr title="kilometers per hour">km/h</abbr></p>
<p> <abbr title="Cascading Style Sheets">CSS</abbr> 用于样式化 <abbr title="HyperText Markup Language">HTML</abbr> 文档。 </p>
|
注意事项:
title 属性提供完整形式
- 鼠标悬停时会显示完整形式
- 屏幕阅读器会读取
title 属性
- 首次出现时应该使用
<abbr>,后续可以省略
<dfn> - 定义术语
用途: 表示正在定义的术语,通常与 <abbr> 或 <p> 配合使用。
使用场景:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <p> <dfn>HTML</dfn>(HyperText Markup Language)是一种标记语言。 </p>
<p> <dfn><abbr title="Cascading Style Sheets">CSS</abbr></dfn> 用于描述 HTML 文档的呈现样式。 </p>
<dl> <dt><dfn>语义化</dfn></dt> <dd>使用合适的 HTML 元素表达内容的含义。</dd> <dt><dfn>无障碍</dfn></dt> <dd>确保网站可以被所有人访问,包括残障人士。</dd> </dl>
|
注意事项:
- 通常用于首次定义术语
- 可以与
<abbr> 嵌套使用
- 屏幕阅读器会特别处理
<dfn> 元素
无障碍基础:ARIA 属性
ARIA 简介
ARIA(Accessible Rich Internet Applications) 是一组属性,用于增强 HTML 的可访问性,特别是对于动态内容和复杂的用户界面。
ARIA 的作用:
- 为屏幕阅读器提供额外的语义信息
- 描述元素的状态和属性
- 改善键盘导航体验
- 补充 HTML 语义的不足
常用 ARIA 属性
aria-label - 标签文本
用途: 为元素提供可访问的名称,覆盖元素的默认标签。
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <button aria-label="关闭对话框"> <span aria-hidden="true">×</span> </button>
<nav aria-label="主导航"> <ul>...</ul> </nav>
<input type="text" aria-label="搜索关键词" placeholder="搜索...">
|
aria-labelledby - 引用标签元素
用途: 通过 ID 引用其他元素作为标签。
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <div> <h2 id="form-title">联系表单</h2> <form aria-labelledby="form-title"> </form> </div>
<table aria-labelledby="table-title"> <caption id="table-title">销售数据表</caption> </table>
|
aria-describedby - 引用描述元素
用途: 通过 ID 引用其他元素作为描述或帮助文本。
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <input type="password" id="password" aria-describedby="password-help password-error" > <span id="password-help">密码应至少包含8个字符</span> <span id="password-error" role="alert">密码格式不正确</span>
<label for="email">邮箱</label> <input type="email" id="email" aria-describedby="email-help"> <span id="email-help">我们将使用此邮箱发送通知</span>
|
aria-hidden - 隐藏装饰性元素
用途: 告诉屏幕阅读器忽略装饰性元素。
示例:
1 2 3 4 5 6 7 8 9 10
| <button aria-label="删除"> <span aria-hidden="true">🗑️</span> </button>
<div> <span aria-hidden="true">⭐</span> <span>评分:4.5</span> </div>
|
注意事项:
- 只用于装饰性元素,不影响内容理解
- 不要隐藏重要的交互元素或内容
aria-live - 动态区域
用途: 指定动态更新的区域,屏幕阅读器会及时通知用户。
值:
polite:等待当前任务完成后再通知(推荐)
assertive:立即中断当前任务并通知
off:不通知(默认)
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| <div role="alert" aria-live="assertive"> 操作成功! </div>
<div aria-live="polite" aria-atomic="true"> 找到 10 条结果 </div>
<span role="alert" aria-live="polite" id="error-message"></span>
|
aria-expanded - 展开/折叠状态
用途: 表示可折叠元素的展开或折叠状态。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <button aria-expanded="false" aria-controls="menu" onclick="toggleMenu()" > 菜单 </button> <ul id="menu" hidden> <li>选项1</li> <li>选项2</li> </ul>
<script> function toggleMenu() { const menu = document.getElementById('menu'); const button = document.querySelector('button'); const isExpanded = menu.hidden === false; menu.hidden = !isExpanded; button.setAttribute('aria-expanded', !isExpanded); } </script>
|
aria-current - 当前项
用途: 表示当前页面或步骤中的当前项。
值:
page:当前页面
step:当前步骤
location:当前位置
date:当前日期
time:当前时间
true:当前项
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <nav aria-label="面包屑导航"> <ol> <li><a href="/">首页</a></li> <li><a href="/products">产品</a></li> <li aria-current="page">当前产品</li> </ol> </nav>
<nav aria-label="分页"> <ol> <li><a href="/page/1">1</a></li> <li aria-current="page">2</li> <li><a href="/page/3">3</a></li> </ol> </nav>
|
ARIA Roles(角色)
常用角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <div role="alert">重要通知</div>
<div role="status" aria-live="polite">操作完成</div>
<div role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"> 50% </div>
<div role="dialog" aria-labelledby="dialog-title" aria-modal="true"> <h2 id="dialog-title">确认删除</h2> <p>确定要删除此项吗?</p> <button>确认</button> <button>取消</button> </div>
<ul role="menu"> <li role="menuitem"><a href="#">选项1</a></li> <li role="menuitem"><a href="#">选项2</a></li> </ul>
|
注意事项:
- 优先使用语义化 HTML 元素(如
<button> 而不是 <div role="button">)
- ARIA 角色用于补充语义,而不是替代语义化 HTML
可聚焦性与键盘导航
Tab 键顺序(Tab Order)
可聚焦元素:
<a>(有 href 属性)
<button>
<input>、<select>、<textarea>
- 设置了
tabindex="0" 的元素
示例:
1 2 3 4 5 6 7 8 9 10
| <input type="text" placeholder="输入1"> <input type="text" placeholder="输入2"> <button>提交</button> <a href="#">链接</a>
<input type="text" tabindex="1"> <input type="text" tabindex="3"> <input type="text" tabindex="2">
|
tabindex 属性:
tabindex="0":按文档顺序可聚焦
tabindex="-1":不可通过 Tab 键聚焦,但可以通过 JS 聚焦
tabindex="1" 或更大:按数值顺序聚焦(不推荐,会破坏自然顺序)
键盘事件处理
常用键盘事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| form.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.submit(); } });
dialog.addEventListener('keydown', (e) => { if (e.key === 'Escape') { dialog.close(); } });
menu.addEventListener('keydown', (e) => { const items = Array.from(menu.querySelectorAll('[role="menuitem"]')); const currentIndex = items.indexOf(document.activeElement); if (e.key === 'ArrowDown') { e.preventDefault(); const nextIndex = (currentIndex + 1) % items.length; items[nextIndex].focus(); } else if (e.key === 'ArrowUp') { e.preventDefault(); const prevIndex = (currentIndex - 1 + items.length) % items.length; items[prevIndex].focus(); } });
|
可见焦点指示器
CSS 焦点样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| :focus { outline: 2px solid #0066cc; outline-offset: 2px; }
button:focus, a:focus, input:focus { outline: 3px solid #ff6600; outline-offset: 2px; box-shadow: 0 0 0 4px rgba(255, 102, 0, 0.3); }
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 3px solid #0066cc; outline-offset: 2px; }
*:focus { outline: none; }
|
注意事项:
- 永远不要完全移除焦点样式
- 如果移除
outline,必须提供替代的视觉指示器(如 box-shadow、border)
- 使用
:focus-visible 可以为键盘用户和鼠标用户提供不同的焦点样式
跳过链接(Skip Links)
用途: 允许键盘用户跳过重复的导航内容,直接到达主要内容。
示例:
1 2 3 4 5 6 7 8 9 10 11
| <body> <a href="#main-content" class="skip-link">跳到主要内容</a> <header>...</header> <nav>...</nav> <main id="main-content"> </main> </body>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .skip-link { position: absolute; top: -40px; left: 0; background: #000; color: #fff; padding: 8px; text-decoration: none; z-index: 100; }
.skip-link:focus { top: 0; }
|
屏幕阅读器友好实践
图片替代文本(Alt Text)
最佳实践:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <img src="photo.jpg" alt="一只橘猫坐在窗台上,阳光洒在它的身上">
<img src="decoration.jpg" alt="" role="presentation">
<img src="chart.png" alt="2025年销售额增长30%,从100万增长到130万">
<a href="/products"> <img src="product.jpg" alt="查看产品详情"> </a>
<a href="/products"> <img src="product.jpg" alt=""> 产品详情 </a>
|
Alt 文本编写原则:
- 简洁明了,通常不超过125个字符
- 描述图片的内容和功能,而不是外观
- 如果图片包含文字,alt 文本应该包含这些文字
- 装饰性图片使用空 alt(
alt="")
表单标签与错误提示
最佳实践:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <label for="email">邮箱地址</label> <input type="email" id="email" name="email" required>
<label> 邮箱地址 <input type="email" name="email" required> </label>
<label for="name"> 姓名 <span aria-label="必填">*</span> </label> <input type="text" id="name" name="name" required aria-required="true">
<label for="password">密码</label> <input type="password" id="password" name="password" aria-invalid="true" aria-describedby="password-error" > <span id="password-error" role="alert">密码必须至少8个字符</span>
<label for="username">用户名</label> <input type="text" id="username" name="username" aria-describedby="username-help" > <span id="username-help">用户名应为3-20个字符,只能包含字母和数字</span>
|
表格可访问性
最佳实践:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <table> <caption>2025年销售数据</caption> <thead> <tr> <th scope="col">月份</th> <th scope="col">销售额</th> <th scope="col">增长率</th> </tr> </thead> <tbody> <tr> <th scope="row">1月</th> <td>100万</td> <td>+5%</td> </tr> <tr> <th scope="row">2月</th> <td>110万</td> <td>+10%</td> </tr> </tbody> </table>
|
scope 属性:
scope="col":列标题
scope="row":行标题
scope="colgroup":列组标题
scope="rowgroup":行组标题
标题层级
最佳实践:
1 2 3 4 5 6 7 8 9 10 11
| <h1>页面主标题</h1> <h2>第一部分</h2> <h3>第一部分的子章节</h3> <h2>第二部分</h2> <h3>第二部分的子章节</h3> <h4>更细的子章节</h4>
<h1>页面主标题</h1> <h3>跳过了 h2,直接使用 h3</h3>
|
注意事项:
- 不要跳过标题层级(如从
<h1> 直接跳到 <h3>)
- 每个页面应该只有一个
<h1>
- 标题应该反映内容的层次结构
常见误区与最佳实践
常见误区
❌ 误区1:使用 <div> 和 <span> 代替语义元素
1 2 3 4 5 6 7 8 9
| <div class="header">...</div> <div class="nav">...</div> <div class="main">...</div>
<header>...</header> <nav>...</nav> <main>...</main>
|
❌ 误区2:移除所有焦点样式
1 2 3 4 5 6 7 8 9 10
| *:focus { outline: none; }
:focus { outline: 2px solid #0066cc; outline-offset: 2px; }
|
❌ 误区3:使用 <div> 作为按钮
1 2 3 4 5
| <div onclick="submit()" class="button">提交</div>
<button onclick="submit()">提交</button>
|
❌ 误区4:图片缺少 alt 文本
1 2 3 4 5
| <img src="photo.jpg">
<img src="photo.jpg" alt="描述图片内容">
|
❌ 误区5:表单控件缺少标签
1 2 3 4 5 6
| <input type="text" placeholder="请输入邮箱">
<label for="email">邮箱</label> <input type="email" id="email" name="email">
|
❌ 误区6:过度使用 ARIA
1 2 3 4 5
| <button role="button" aria-label="提交">提交</button>
<button>提交</button>
|
最佳实践清单
✅ 语义化 HTML
✅ 图片和媒体
✅ 表单
✅ 键盘导航
✅ ARIA
✅ 测试
无障碍检查工具
浏览器扩展:
- axe DevTools:Chrome、Firefox 扩展
- WAVE:Chrome、Firefox 扩展
- Lighthouse:Chrome 内置工具
在线工具:
命令行工具:
- axe-core:npm 包
- pa11y:命令行无障碍测试工具
总结
语义化 HTML 和无障碍设计不仅是为了满足法律要求,更是为了创建更好的用户体验。通过:
- 使用语义化 HTML 元素,让内容结构清晰
- 正确使用 ARIA 属性,补充语义信息
- 确保键盘可访问性,支持所有用户
- 提供清晰的焦点指示器,帮助用户导航
- 编写有意义的替代文本,让图片可访问
- 正确标记表单,帮助用户理解和使用
我们可以创建出真正包容、可访问的网站,让所有人都能使用我们的产品。
记住:无障碍不是功能,而是权利。