0%

语义化与无障碍(A11y)

语义化与无障碍(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>&copy; 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>

<!-- 与 abbr 配合 -->
<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
<!-- 自然的 Tab 顺序 -->
<input type="text" placeholder="输入1">
<input type="text" placeholder="输入2">
<button>提交</button>
<a href="#">链接</a>

<!-- 自定义 Tab 顺序(不推荐) -->
<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
// Enter 键提交
form.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
form.submit();
}
});

// Escape 键关闭对话框
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-shadowborder
  • 使用 :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
<!-- 方法1:label + input -->
<label for="email">邮箱地址</label>
<input type="email" id="email" name="email" required>

<!-- 方法2:label 包裹 input -->
<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
<!-- 错误:HTML 已经有语义,不需要 ARIA -->
<button role="button" aria-label="提交">提交</button>

<!-- 正确:使用语义化 HTML -->
<button>提交</button>

最佳实践清单

✅ 语义化 HTML

  • 使用语义化元素(<header><nav><main><article><section><aside><footer>
  • 使用表意元素(<figure><time><address><abbr><dfn>
  • 正确使用标题层级(<h1>-<h6>
  • 使用 <button> 而不是 <div> 作为按钮

✅ 图片和媒体

  • 所有有意义的图片都有 alt 文本
  • 装饰性图片使用空 altalt=""
  • 视频和音频提供字幕或文字说明

✅ 表单

  • 所有表单控件都有 <label>
  • 必填字段明确标记
  • 错误提示清晰可见
  • 使用 aria-describedby 关联帮助文本

✅ 键盘导航

  • 所有交互元素都可以通过键盘访问
  • Tab 顺序合理
  • 焦点样式清晰可见
  • 提供跳过链接

✅ ARIA

  • 只在必要时使用 ARIA
  • 优先使用语义化 HTML
  • 动态内容使用 aria-live
  • 可折叠元素使用 aria-expanded

✅ 测试

  • 使用屏幕阅读器测试(NVDA、JAWS、VoiceOver)
  • 仅使用键盘测试导航
  • 使用无障碍检查工具(axe、WAVE、Lighthouse)
  • 在不同浏览器中测试

无障碍检查工具

浏览器扩展:

  • axe DevTools:Chrome、Firefox 扩展
  • WAVE:Chrome、Firefox 扩展
  • Lighthouse:Chrome 内置工具

在线工具:

命令行工具:

  • axe-core:npm 包
  • pa11y:命令行无障碍测试工具

总结

语义化 HTML 和无障碍设计不仅是为了满足法律要求,更是为了创建更好的用户体验。通过:

  1. 使用语义化 HTML 元素,让内容结构清晰
  2. 正确使用 ARIA 属性,补充语义信息
  3. 确保键盘可访问性,支持所有用户
  4. 提供清晰的焦点指示器,帮助用户导航
  5. 编写有意义的替代文本,让图片可访问
  6. 正确标记表单,帮助用户理解和使用

我们可以创建出真正包容、可访问的网站,让所有人都能使用我们的产品。

记住:无障碍不是功能,而是权利。