HTML5语义化标签与应用

张玥 2026年9月18日 阅读时间 20分钟
HTML5 语义化 无障碍 SEO 最佳实践
HTML5语义化标签与应用

“语义化”这三个字,在前端圈被念叨了十几年,但真正把标签用对的项目依然是少数。随手打开一个线上站点,你大概率还是会看到成片的 <div class="header">、<div class="nav">、<div class="main">——用 class 名字把标签的语义“喊”出来,却在结构上什么都没告诉浏览器。HTML5 在 2014 年成为 W3C 正式推荐标准,为 Web 带来了一整套表达结构的原生标签。它们不改变一个像素的视觉效果,却决定了屏幕阅读器能否读懂你的页面、搜索引擎能否理解你的内容、半年后的你能否快速找回那段代码。本篇 20 分钟长文,从“什么是语义化”讲起,逐一拆解结构标签、文本级标签、表单、媒体、交互元素与文档大纲,并给出可落地的重构方法与检查清单。

一、什么是语义化,以及它到底值多少钱

语义化(Semantic HTML)的核心只有一句话:用最能表达内容含义的标签,去承载这段内容。标题就用 h1,不要用 <div class="title">;导航就用 nav,不要用 <div class="nav">;一段强调就用 strong,不要用 <span style="font-weight:bold">。

这不是审美偏好,而是因为你的 HTML 文档同时有三类“读者”,而它们都看不见你的 CSS:

人类读者 半年后的你、新入职的同事,靠标签结构就能推断页面意图
机器读者 搜索引擎爬虫、社交平台摘要抓取、AI 训练与检索系统
辅助技术 屏幕阅读器、语音控制、盲文显示器、键盘导航用户
浏览器 阅读模式、翻译、自动填充、密码管理器都依赖语义线索

从 div 汤到结构化文档

在 HTML4 时代,页面结构只能靠 id 和 class 表达。浏览器知道那是一个 div,但不知道它是导航还是正文;屏幕阅读器只能顺序朗读,无法提供“跳到主内容”这类快捷操作;爬虫要判断哪块是正文,只能靠一堆启发式规则去猜。

HTML5 的做法是:把这套“约定俗成”的命名模式固化成真正的元素。于是 header、nav、main、article、section、aside、footer、figure、time、mark 等一批标签进入了标准。它们默认的 CSS 表现与 div 几乎完全一致(多为 display: block),但携带了浏览器与辅助技术可以直接消费的语义信息。

语义 ≠ 样式,这是最容易混淆的一点

很多开发者抗拒语义化标签的理由是:“反正我都要写 CSS,用 div 也一样”。这句话混淆了两个层次:

  • 语义层回答“这是什么”——一个独立的文章、一组导航、一段被强调的文字。
  • 表现层回答“它长什么样”——字号、颜色、间距、圆角。

HTML 负责前者,CSS 负责后者。用 h1 不意味着它必须很大很粗——你完全可以用 CSS 把它做成 14px 的小字。反过来,把大字号写在 div 上,也不会让它变成一个标题。标签选择应该由内容语义决定,而不是由视觉效果倒推。

常见误区
“这个标题我要做成小字,所以用 div 更合适。”——这是在用视觉反推结构,屏幕阅读器会完全读不到这个标题。
正确姿势
结构上用 h2,视觉上用 CSS 覆盖:font-size: 0.95rem;。语义与样式各司其职。

二、文档结构标签:搭好页面的骨架

结构标签是 HTML5 语义化最重要的部分。它们共同构成了页面的“地标”(Landmark),让辅助技术用户可以像使用 App 底部导航一样,在页面各大区域之间快速跳转。点击下面的每个标签,看看它承担什么职责。

1
header
页头 / 区块头
存放介绍性内容:站点 Logo、页面主标题、搜索框、面包屑。可以出现在 body 顶部,也可以出现在 article 或 section 内部,表示该区块的头部。
2
nav
导航
只用于主要的导航链接组。屏幕阅读器会把它识别为 landmark,用户可一键跳转。页面底部的版权链接列表通常不需要 nav。
3
main
主内容区
承载页面唯一的、核心的内容。一个文档只应有一个可见的 main,且不能嵌套在 article、aside、nav、header、footer 内部。
4
article
独立内容单元
判断标准:这段内容脱离当前页面单独分发,是否依然完整有意义?博客正文、论坛帖子、用户评论、商品卡片、新闻条目都符合。
5
section
主题分组
一组围绕同一主题的内容,通常应该带一个标题(h2~h6)。如果一个“section”你只是为了加背景色或内边距,那它应该继续用 div。
6
aside
侧边 / 附属内容
与主内容间接相关的内容:作者简介、相关推荐、广告位、术语表、引用框。用 aside 会让屏幕阅读器用户可以主动跳过它。
7
footer
页脚 / 区块脚
存放版权、备案、联系方式、相关链接。与 header 一样,既可以是页面级,也可以是 article/section 级,表示该区块的结尾信息。

一张骨架示例

把上面这些标签按真实页面组合起来,结构大致如下:

<body>
  <header>
    <a class="logo">前端开发技巧</a>
    <nav>
      <ul>
        <li><a href="/">首页</a></li>
        <li><a href="/css">CSS</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <header>
        <h1>高性能 CSS 动画技巧</h1>
        <p><time datetime="2026-09-18">2026年9月18日</time></p>
      </header>

      <section>
        <h2>渲染流水线</h2>
        <p>……</p>
      </section>

      <aside>
        <h2>相关阅读</h2>
      </aside>

      <footer>
        <p>© 2026 前端开发技巧</p>
      </footer>
    </article>
  </main>

  <footer>
    <p>备案信息……</p>
  </footer>
</body>

容易踩坑的四条规则

  • main 只能有一个:HTML 规范要求一个文档中只能有一个非 hidden 的 main,并且它不能是 article、aside、footer、header、nav 的后代。
  • header / footer 不限于页面级:写在 article 里就是这篇文章的页眉页脚,可以出现多次。很多开发者误以为它们只能出现一次。
  • section 必须有标题:如果没有合适的标题,说明它不是一个“主题分组”,请退回使用 div。
  • nav 要克制:不是所有链接集合都叫导航。把页脚的一堆链接、文章里的“相关阅读”都塞进 nav,反而会让地标列表变得嘈杂。

div 与 span 依然不可替代

语义化不等于“消灭 div”。div 的定位是无语义的通用容器,用于分组、布局、挂载样式或脚本钩子。当你需要一层纯粹为了 flex 布局的包裹元素时,div 就是最正确的选择——给它套上一个 section 反而是在撒谎。

标签 语义 典型场景 常见误用
header 介绍性内容 站点页头、文章标题区 当成“页面顶部固定条”的样式容器
nav 主要导航 主导航、面包屑、分页 把所有链接列表都包成 nav
main 页面主内容 每页唯一一次 嵌套在 article 内部
article 可独立分发 文章、评论、卡片 把整页内容包一层当成“大容器”
section 主题分组 带标题的章节 纯样式包裹、没有标题
aside 附属内容 侧边栏、引用框、广告 把主要内容放进去
footer 结尾信息 版权、备案、作者署名 里面塞主内容导航
div 无语义容器 布局、样式钩子 本该用语义标签时一律用它

三、文本级语义:让每个词都有出处

结构标签决定页面的大块划分,文本级标签则决定句子内部每个片段的性质。它们大多是行内元素,视觉上与 span 无异,但语义差别巨大——尤其是对屏幕阅读器而言,strong 会被改变语调,em 会被加重读,abbr 可以被展开。

强调类:strong 与 em

这两个标签最容易被误用。关键在于区分“重要性”和“重音”:

  • strong 表示重要性、严重性、紧急性。屏幕阅读器通常会加重语气。b 只是视觉加粗,没有强调语义,用于产品名、关键词高亮等场景。
  • em 表示重音强调,会改变句子的意思。比如“我没说你偷了钱”和“我没说你偷了钱”,重音不同,含义完全不同。i 则用于外语词、科技术语、心语等,不表示强调。

经验法则:如果删掉这个标签,句子含义不变,那它就不该用 strong/em,用 b/i 或者干脆不用。

引用与来源:blockquote、q、cite

<blockquote cite="https://example.com/source">
  <p>语义化不是让浏览器看得懂,而是让所有读者都看得懂。</p>
  <footer>—— <cite>某位前端工程师</cite></footer>
</blockquote>

<!-- 短引用用 q,浏览器会自动加引号 -->
<p>他常说 <q>先写结构,再想样式</q>。</p>

<!-- cite 用于作品名,不是人名! -->
<p>推荐阅读 <cite>HTML 标准</cite>。</p>
blockquote 块级引用,可嵌套 footer 标注出处
q 行内引用,浏览器自动补充引号
cite 作品标题(书名、歌名、标准名)
注意 cite 属性不等于 cite 标签,且人名不该用 cite

时间与缩写:time 与 abbr

time 是 HTML5 中性价比极高的一个标签。它的 datetime 属性让机器可以精确解析时间,而页面显示可以是任意人读格式:

<time datetime="2026-09-18">2026年9月18日</time>
<time datetime="2026-09-18T14:30">今天下午两点半</time>
<time datetime="PT2H30M">两个半小时</time>

<abbr title="Cascading Style Sheets">CSS</abbr>
<abbr title="HyperText Markup Language">HTML</abbr>

搜索引擎会利用 datetime 判断内容时效性,社交平台抓取摘要时也会优先读取它。这是少数“只写一行代码就能带来实际 SEO 收益”的标签。

代码与键盘:code、pre、kbd、samp、var

<p>使用 <code>display: flex</code> 开启弹性布局。</p>

<!-- pre 保留空白与换行,常与 code 嵌套 -->
<pre><code>
  const sum = (a, b) => a + b;
</code></pre>

<!-- kbd:用户按下的键 -->
<p>按 <kbd>Ctrl</kbd> + <kbd>S</kbd> 保存。</p>

<!-- samp:程序输出 -->
<p><samp>Build succeeded.</samp></p>

<!-- var:变量或占位符 -->
<p>函数 <var>fn</var> 接收参数 <var>x</var>。</p>
code 行内代码片段
pre 保留原始空白格式的块
kbd 用户键盘输入
samp 程序输出结果
var 数学/编程变量

其余高频文本标签速查

  • mark:与当前上下文相关的高亮,例如搜索结果命中词。它自带黄底,比用 span + 背景色更有语义。
  • del / ins:文档修订中的删除与插入,配合 datetime 与 cite 可记录修改时间与原因。
  • s:表示内容已不再准确或不再相关,例如原价划掉。注意与 del 的区别——s 不是修订痕迹。
  • small:附属细则、法律声明、版权信息。它不是“小字号的通用容器”。
  • sub / sup:下标与上标,如 H2O、x2。如果只是为了排版错位,不要用它们。
  • dfn:术语的首次定义处。常与 abbr 搭配使用。
  • address:最近的 article 或 body 的联系信息,通常是邮箱、电话、地址,不要用它包裹任意地址文本。
  • bdi / bdo:处理双向文本(如混排阿拉伯语、希伯来语)时的方向隔离与覆盖。
  • ruby / rt / rp:东亚文字的注音标注,如振假jiǎ名。
  • wbr:可换行断点,告诉浏览器“实在放不下时可以在这里断行”,常用于长 URL。
反例
<span style="font-weight:bold">警告:</span>此操作不可撤销。
屏幕阅读器读起来毫无波澜,用户完全感受不到“警告”的分量。
正解
<strong>警告:</strong>此操作不可撤销。
语义化的强调,多数屏幕阅读器会改变语调或插入提示。

四、列表、表格与表单:结构化数据的三个重灾区

列表:ul、ol、dl 各有分工

列表是导航菜单、文章目录、步骤说明的标准结构。除了 ul 与 ol,HTML5 还保留了一个常被忽略的 dl(描述列表):

<!-- 无序列表:顺序不重要 -->
<ul>
  <li>语义化</li>
  <li>无障碍</li>
</ul>

<!-- 有序列表:顺序有意义,可用 start / reversed -->
<ol start="3" reversed>
  <li>第三步</li>
  <li>第二步</li>
</ol>

<!-- 描述列表:术语与解释成对出现 -->
<dl>
  <dt>语义化</dt>
  <dd>用恰当的标签表达内容的含义与结构。</dd>
  <dt>无障碍</dt>
  <dd>让所有人,包括使用辅助技术的用户,都能平等使用产品。</dd>
</dl>

表格:不要用 table 做布局,但数据一定要用 table

过去十年“不要用 table 布局”的呼吁,让不少开发者走向另一个极端:连数据表格也用 div 拼。这是错的。数据表格用 div 搭建后,屏幕阅读器无法朗读“第 3 行第 2 列”,用户彻底失去行列关系。

一个无障碍友好的数据表格,应该包含以下要素:

  • caption:表格标题,屏幕阅读器进入表格时会优先朗读它。
  • thead / tbody / tfoot:区分表头、主体、表尾,让长表格滚动时表头可保持。
  • th + scope="col" 或 scope="row":明确表头作用范围。
  • 复杂表格用 id + headers 建立单元格与表头的显式关联。
<table>
  <caption>2026 年第二季度各浏览器市场份额</caption>
  <thead>
    <tr>
      <th scope="col">浏览器</th>
      <th scope="col">份额</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Chrome</th>
      <td>65.4%</td>
    </tr>
  </tbody>
</table>

表单:语义化的收益最直接

表单是用户与页面交互最密集的地方,也是语义化收益最明显的地方。一个语义正确的表单,能自动获得:点击标签聚焦输入框、屏幕阅读器正确朗读、浏览器自动填充、移动端弹出合适的键盘类型。

<form action="/subscribe" method="post">
  <fieldset>
    <legend>订阅资讯</legend>

    <!-- label 的 for 与 input 的 id 一一对应 -->
    <label for="email">邮箱地址</label>
    <input
      type="email"
      id="email"
      name="email"
      autocomplete="email"
      required
      aria-describedby="email-hint">
    <p id="email-hint">我们不会向第三方分享你的邮箱。</p>

    <button type="submit">订阅</button>
  </fieldset>
</form>
label 点击文字即可聚焦输入框,屏幕阅读器朗读标签名
fieldset 把相关控件分组,legend 提供组标题
type email / tel / url / date 触发校验与专用键盘
autocomplete 让浏览器与密码管理器准确识别字段含义
placeholder 不能替代 label!它输入后即消失

一个高频错误是用 placeholder 充当标签。placeholder 在用户开始输入后就消失了,用户无法回头确认这个字段到底要填什么;屏幕阅读器对它的支持也参差不齐。正确做法是始终提供可见的 label,placeholder 只作为格式示例。

另一个细节是 button 的 type 属性。在 form 内部,不写 type 的 button 默认是 submit,很容易导致“点了个普通按钮却把表单提交了”。任何非提交按钮都请显式写上 type="button"。

五、媒体与嵌入:图、音、视的语义表达

figure 与 figcaption:让配图有说明

正文中的插图、代码清单、图表,往往需要一段说明文字。过去只能用 div 包一层 img 再加一个 p,语义上是一片空白。figure 与 figcaption 正是为此而生:

<figure>
  <img src="chart.png" alt="2026 年各季度访问量趋势图">
  <figcaption>图 1:访问量在第三季度出现明显增长</figcaption>
</figure>

<!-- figure 里也可以放代码清单 -->
<figure>
  <pre><code>npm run build</code></pre>
  <figcaption>清单 1:构建命令</figcaption>
</figure>

figcaption 必须是 figure 的第一个或最后一个子元素,位置决定它显示在媒体上方还是下方。

picture 与 source:响应式图片的语义方案

很多开发者只知道 srcset,却忽略了 picture。两者的分工是:

  • srcset + sizes:同一张图的不同分辨率,由浏览器根据 DPR 与视口宽度自行选择。
  • picture + source:艺术指导场景,不同条件用不同构图或格式,例如桌面端用横图、移动端用竖图,或优先使用 AVIF/WebP。
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <source
    media="(max-width: 600px)"
    srcset="hero-mobile.jpg">
  <img
    src="hero.jpg"
    alt="团队成员在会议室讨论方案"
    width="1200"
    height="600"
    loading="lazy">
</picture>

注意最后那个 img:它是必需的,承担实际的渲染、alt 文本与尺寸占位。同时写上 width 与 height 可以避免图片加载完成后的布局偏移(CLS),这也是语义化之外一个常被忽视的性能细节。

video、audio 与 track

媒体元素的原生语义能力包括:controls 提供无障碍控件,poster 指定封面,track 提供字幕与说明:

<video controls poster="cover.jpg" width="960" height="540">
  <source src="intro.webm" type="video/webm">
  <source src="intro.mp4" type="video/mp4">

  <!-- 字幕:听力障碍用户的必需品 -->
  <track kind="captions" src="zh.vtt" srclang="zh" label="中文" default>
  <track kind="descriptions" src="desc.vtt" srclang="zh">

  <!-- 兜底内容:浏览器不支持时显示 -->
  您的浏览器不支持视频播放,<a href="intro.mp4">点击下载</a>。
</video>

track 的 kind 有多种取值:captions(含音效描述的字幕)、subtitles(翻译字幕)、descriptions(画面描述,供视障用户)、chapters(章节导航)、metadata(元数据)。只要视频里有说话,就应该提供 captions。

iframe 与 canvas 的语义补充

  • iframe 必须写 title 属性,否则屏幕阅读器只能读成“框架”,用户不知道里面是什么。嵌入第三方内容时,配合 loading="lazy" 与 sandbox 使用。
  • canvas 本身没有任何语义,里面的内容对辅助技术完全不可见。必须在标签内部提供回退内容,或使用 role="img" + aria-label 描述图形含义。
  • svg 内联到页面时,装饰性图标应加 aria-hidden="true" 与 focusable="false";有含义的图标则加 role="img" 与 <title>。

六、原生交互元素:不写 JS 也能用

HTML5 引入了一批原生交互元素,它们自带键盘支持、焦点管理和无障碍语义。能用原生元素的场合,永远不要用 div + JS 手搓。

details 与 summary:原生手风琴

<details>
  <summary>语义化真的能提升 SEO 吗?</summary>
  <p>会。搜索引擎会解析文档结构,语义清晰的页面更容易被正确理解与提取摘要。</p>
</details>

<!-- 用 name 属性让多个 details 互斥(手风琴效果) -->
<details name="faq">…</details>
<details name="faq">…</details>

details 天生支持键盘操作(Tab 聚焦、Enter/Space 展开)、支持屏幕阅读器朗读“展开/折叠”状态,还自带 toggle 事件。open 属性可以控制默认展开。它是 FAQ、折叠面板的最优解。

dialog:原生模态框

自己实现的模态框,最容易出问题的地方在于焦点管理:打开时焦点没有移入、关闭时没有归还、Tab 能跑到背后的内容上、屏幕阅读器还能读到后面的内容。dialog 把这些全部内置了:

<dialog id="confirm">
  <h2>确认删除?</h2>
  <p>删除后无法恢复。</p>
  <form method="dialog">
    <button value="cancel">取消</button>
    <button value="ok">删除</button>
  </form>
</dialog>

// 模态打开:自动聚焦、自动锁定背景、Esc 关闭
dialog.showModal();

// 非模态(更像 popover)
dialog.show();

配合 ::backdrop 伪元素可以自定义遮罩层样式,配合 closedby 属性还能控制点击遮罩是否关闭。

progress 与 meter

  • progress:表示任务的完成进度。value 与 max 都存在时是确定进度,只写 max 或都不写则是“不确定进度条”(转圈动画)。
  • meter:表示某个已知范围内的标量值,例如磁盘占用、评分、温度。low / high / optimum 可以让浏览器自动渲染出不同的颜色语义。
<label for="upload">上传进度</label>
<progress id="upload" value="70" max="100">70%</progress>

<label for="disk">磁盘占用</label>
<meter id="disk" value="0.82" min="0" max="1"
       low="0.3" high="0.8" optimum="0.2">82%</meter>

template 与 slot:Web Components 的基石

template 里的内容在页面加载时不会被渲染、不会执行脚本、不会加载图片,直到被 JS 克隆出来才“生效”。它是声明式渲染与组件化的基础:

<template id="card">
  <article class="card">
    <h3><slot name="title"></slot></h3>
    <p><slot></slot></p>
  </article>
</template>

<my-card>
  <span slot="title">语义化标签</span>
  用原生元素表达结构含义。
</my-card>

七、文档大纲与标题层级

HTML5 早期曾设计过一套“大纲算法”(Outline Algorithm),允许每个 section 拥有自己的 h1 并由浏览器计算出层级。但这套算法从未被任何浏览器或屏幕阅读器真正实现,如今已被规范移除。所以,现实世界的规则回到了最朴素的一条:

用 h1~h6 的层级数字,手工表达标题的嵌套深度,不要跳级。

h1 页面主标题,一个页面通常只出现一次
h2 一级章节标题,对应文章中的“大节”
h3 二级小节标题,隶属于某个 h2
h4 ~ h6 更深层级,超过 h4 通常说明内容需要拆分页面
禁止 从 h1 直接跳到 h3;用 h4 仅仅因为它“字号刚好合适”

屏幕阅读器的“按标题浏览”

主流屏幕阅读器(NVDA、JAWS、VoiceOver)都提供“按标题跳转”的快捷键。用户打开一个长页面,第一件事往往就是拉出标题列表,快速判断这个页面有没有自己要找的内容。

如果你的标题层级混乱——比如满屏 h3、或者所有标题都是 <div class="title">——这份列表就是空的,用户只能一个字一个字地听下去。这不是“体验差一点”,而是直接把人挡在门外。

视觉层级与语义层级分离

常见的两难是:设计稿上某个标题看起来不大,用 h2 会导致字号过大。解决办法永远是 CSS:

/* 语义上用 h2,视觉上完全自定义 */
.sidebar h2 {
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* 只在视觉上移除标题,但保留给屏幕阅读器 */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* 千万不要用 display: none 隐藏标题——它也会被辅助技术忽略 */

这个 .visually-hidden 技巧用途很广:给没有可见标题的区块(例如侧边栏的相关推荐列表)补一个标题,或者给“跳转到主内容”的链接提供文本。

跳转到主内容:一个只需三行代码的优化

键盘用户在每个页面都要先 Tab 过整个导航栏才能到达正文。一个“跳到主内容”的链接可以省掉这几十次按键:

<a href="#main" class="skip-link">跳到主内容</a>

<main id="main" tabindex="-1">……</main>

/* 平时隐藏在视口外,获得焦点时滑入 */
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 999;
}
.skip-link:focus {
  left: 10px;
  top: 10px;
  background: #2563eb;
  color: white;
  padding: 8px 16px;
}

八、无障碍:语义化最大的受益者

如果说语义化只对一件事产生决定性影响,那一定是无障碍。屏幕阅读器用户浏览网页的主要方式有三种,每一种都建立在语义之上:

  • 按地标跳转:在 header、nav、main、aside、footer 之间快速切换,跳过不关心的区域。
  • 按标题跳转:拉出标题列表,快速定位到目标章节。
  • 按元素类型跳转:直接跳转到下一个链接、按钮、表单控件或列表。

这三种方式全部依赖正确的标签。一个用 div 拼出来的页面,这三种导航方式全部失效。

原生语义 > ARIA

ARIA(Accessible Rich Internet Applications)提供了一套属性,可以给无语义的元素“补充”语义。但 WAI-ARIA 规范的第一条规则就是:

如果存在一个原生的 HTML 元素或属性能够表达所需的语义和行为,就使用它,而不是重新定义一个。

原因很简单:原生元素自带键盘交互、焦点管理、状态同步和跨浏览器兼容,而 role 只是给辅助技术“贴了个标签”,行为还得你自己实现。比如:

反例
<div role="button" tabindex="0" onclick="...">提交</div>
需要自己处理 Enter/Space 键、禁用状态、焦点环,还容易被辅助技术误报。
正解
<button type="button">提交</button>
键盘、焦点、屏幕阅读器、表单行为全部原生支持,零额外代码。

五个零成本的无障碍细节

  • 图片写 alt:有含义的图写描述,纯装饰的图写 alt=""(空字符串,而非省略)。省略 alt 会让屏幕阅读器朗读文件名。
  • 链接文本要有意义:避免满屏“点击这里”。屏幕阅读器用户常常会拉出链接列表,一堆“点击这里”毫无信息量。用 aria-label 或视觉隐藏文本补充上下文。
  • 表单控件必须有标签:label 是标准方案,aria-label 是退路,placeholder 不能替代。
  • 不要用颜色单独传递信息:错误提示只标红是不够的,要配上图标或文字。色盲用户看不出差别。
  • 保持焦点可见:不要写 outline: none 而不给替代样式。键盘用户靠焦点环判断自己在哪里。

用辅助技术自测

不需要安装专业软件,你就能做一次基础检查:

  1. 拔掉鼠标,只用 Tab / Shift+Tab / Enter / Space 走一遍页面,看能否完成所有操作。
  2. 打开 macOS 的 VoiceOver(Cmd+F5)或 Windows 的 Narrator,听听它读出来的地标和标题列表是否合理。
  3. 在 DevTools 的 Elements 面板中查看 Accessibility 侧栏,可以看到浏览器为每个元素计算出的可访问名称与角色。
  4. 用 Lighthouse 的 Accessibility 审计跑一遍,重点关注“元素是否使用了正确的角色”这类提示。

九、SEO 与机器可读:语义化的额外红利

语义化不是 SEO 的银弹,但它确实能为搜索引擎提供更准确的信号。搜索引擎爬虫在解析页面时,会利用标签语义来判断内容的层级与重要性。

爬虫如何理解你的页面

  • main:帮助算法区分正文与页头页脚导航,减少“正文提取”环节的噪声。这对内容型站点尤其重要。
  • article:标明这是一个可独立分发的内容单元,有助于识别内容边界。
  • h1~h6:构成内容大纲,帮助算法理解主题与子主题的关系,也常用于生成页面摘要。
  • time + datetime:提供机器可读的发布时间,影响时效性排序与“最新内容”的判定。
  • figure / figcaption:说明图片与正文的关联,图片搜索时会参考这段文字。
  • a 的 rel 属性:rel="nofollow"、rel="sponsored"、rel="ugc" 向爬虫声明链接的性质。

结构化数据:在语义化之上的增强

语义化标签解决的是“页面结构”,而 JSON-LD 结构化数据解决的是“实体关系”。两者配合,可以让搜索结果中出现富媒体摘要(评分、价格、FAQ 折叠、面包屑):

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "HTML5语义化标签与应用",
  "datePublished": "2026-09-18",
  "author": {
    "@type": "Person",
    "name": "张玥"
  }
}
</script>

注意:结构化数据必须与页面可见内容一致。用 JSON-LD 声明了一条并不存在的 FAQ,属于违反搜索引擎指南的行为,可能导致手动惩罚。

面向 AI 时代的可读性

随着大模型成为新的“内容消费者”,语义化的价值又多了一层。当检索系统或 AI 爬虫需要从页面中提取正文时,它们同样依赖结构信号:

  • 清晰的 main 与 article 边界,能让提取算法准确圈定正文范围。
  • 正确的标题层级,能让模型理解内容的分段与从属关系,生成更准确的摘要。
  • 语义化的列表与表格,能让结构信息在纯文本转换后依然保留。
  • 合理的 header / footer 划分,能减少导航噪声对内容提取的干扰。

换句话说,你今天写下的每一个语义标签,都在为未来十年的机器读者铺路。

十、八个最常见的语义化误区

误区 1 把 section 当成“比 div 更高级的 div”,见块就套,且从不给标题
误区 2 一个页面写了五个 h1,或者标题从 h1 直接跳到 h4
误区 3 用 role="button" + div 替代原生 button
误区 4 把 nav 用在页脚的一堆零散链接上
误区 5 用 <br> 制造段落间距,用 <div> 充当段落
误区 6 用 placeholder 当标签,或用 title 属性传关键信息
误区 7 为了 SEO 堆砌 h1 和关键词,导致结构失真
误区 8 认为“加了 aria-label 就等于无障碍”,却忽略了原生语义

两个需要展开说的点

关于 <br>:它只能用于真正的换行语义,例如诗歌、地址、歌词。用它来制造段落间距,屏幕阅读器不会读出停顿,搜索引擎也看不到段落划分。段与段之间应该用 p,间距交给 CSS 的 margin。

关于 title 属性:浏览器对 title 的呈现方式(悬停提示)在触屏设备上完全不可用,键盘用户也难以触发,屏幕阅读器的支持程度参差不齐。因此 title 只能作为补充,关键信息必须写在可见文本里。abbr 的 title 是个例外,因为它本身就是解释性内容。

十一、重构实战:把 div 汤改造成语义结构

下面是一段典型的“div 汤”页面骨架。它用 class 名字描述了所有语义,但在 HTML 层面什么都没说。点击按钮对比改造前后的结构。

<div class="page">
  <div class="top">
    <div class="logo">前端开发技巧</div>
    <div class="menu">
      <div class="menu-item"><a href="/">首页</a></div>
      <div class="menu-item"><a href="/css">CSS</a></div>
    </div>
  </div>

  <div class="content">
    <div class="post">
      <div class="post-title">语义化标签</div>
      <div class="post-date">2026-09-18</div>
      <div class="post-body">正文内容……</div>
    </div>
    <div class="side">作者简介</div>
  </div>

  <div class="bottom">© 2026</div>
</div>

改造带来的六个具体收益

  • 屏幕阅读器:可以按地标跳转,一键直达 main 内容;导航被朗读为“导航,包含 2 个项目”。
  • 阅读模式:浏览器的 Reader Mode 能准确提取正文,不再把侧边栏和页脚一起抓进去。
  • 搜索引擎:正文边界清晰,摘要提取更准确;time 提供了可靠的发布时间。
  • 可维护性:新人接手时,看标签就知道结构,不必逐个查 class 的用途。
  • 体积:大量 class="post-title" 之类的重复命名可以删掉,HTML 更短。
  • 健壮性:即使 CSS 完全加载失败,页面依然保持正确的文档层级,可读性远高于纯 div 版本。

唯一需要注意的是:重构语义标签会改变 CSS 选择器。class 大量依赖标签名的项目,需要同步调整样式。推荐的做法是保留原有 class 作为样式钩子,只把标签换成语义元素——这样 CSS 完全不用改,收益却立刻到手。

十二、兼容性与渐进增强

HTML5 语义标签在今天已经可以放心使用,但仍有几个历史包袱值得了解。

IE8 及更早版本

这些老版本浏览器不认识 header、nav 等新标签,会把它们当作未知内联元素处理,导致样式失效。经典解决方案有两步:

<!-- 1. 让浏览器认识这些元素 -->
<script>
  document.createElement('header');
  document.createElement('nav');
  document.createElement('main');
  document.createElement('article');
  document.createElement('section');
  document.createElement('aside');
  document.createElement('figure');
  document.createElement('figcaption');
  document.createElement('footer');
</script>

/* 2. 让它们表现为块级元素 */
header, nav, main, article, section, aside, figure, figcaption, footer {
  display: block;
}

今天几乎所有项目都已经不再需要这段代码,但了解它的存在有助于理解“为什么早期有人抗拒语义化标签”。

现代浏览器的支持情况

特性 支持情况 使用建议
结构标签(header/nav/main/…) 全平台 放心使用
文本级标签(time/mark/abbr/…) 全平台 放心使用
figure / figcaption 全平台 放心使用
picture / source 广泛支持 保留 img 兜底即可
details / summary 广泛支持 放心使用,样式可用 CSS 定制
dialog(含 showModal) 现代浏览器 老项目需准备降级方案
dialog 的 closedby 较新特性 仅作增强,不要依赖
details 的 name 互斥 现代浏览器 作为增强,JS 方案兜底
template / slot 广泛支持 Web Components 的基础

渐进增强的思路

语义标签的天然优势在于:它们本身就是渐进增强的。即使浏览器不认识某个标签,它仍然是一个可以承载内容、可以被 CSS 选择器命中的元素。所以正确的心态是:

  • 结构语义全部用原生标签,保证任何环境下都能工作。
  • 样式与交互效果作为增强层,用 @supports 或特性检测控制。
  • 不要为了兼容一个占比极低的旧浏览器,放弃整个页面的语义结构。

十三、语义化检查清单与代码基线

把全文的结论浓缩成一份可以贴在工位上的清单。每次提交页面前扫一眼,能挡掉绝大多数问题。

  • 页面只有一个 main,且没有被 article / aside / nav 包裹。
  • 地标完整:页面有明确的 header、nav、main、footer 区域。
  • 标题层级不跳级:h1 → h2 → h3 依次递进,全页只有一个 h1。
  • 每个 section 都有标题,没有标题的容器请用 div。
  • article 判断标准:内容单独拿出来是否依然完整?
  • 导航用 nav + ul / li / a,不要用一堆 div 包链接。
  • 按钮用 button,链接用 a,绝不混用。
  • 表单控件有 label,for 与 id 一一对应。
  • 图片有 alt,装饰性图片写 alt=""。
  • 时间用 time + datetime,机器可读。
  • 引用用 blockquote / q,作品名用 cite。
  • 代码用 code / pre,键盘按键用 kbd。
  • 数据表格用 table,带 caption 与 th scope。
  • 不用 <br> 制造间距,段落用 p,间距交给 CSS。
  • 不用 title 传递关键信息,触屏与键盘用户拿不到。
  • 保留焦点可见样式,不写裸的 outline: none。
  • 只按 Tab 键走一遍页面,确认所有交互都能完成。
  • 重构成语义标签时保留原有 class,让 CSS 无需改动。
<!-- 一份可直接复用的页面语义骨架 -->

<body>
  <a href="#main" class="skip-link">跳到主内容</a>

  <header>
    <a href="/" class="logo">站点名</a>
    <nav aria-label="主导航">
      <ul>……</ul>
    </nav>
  </header>

  <main id="main">
    <article>
      <header>
        <h1>文章标题</h1>
        <p>
          <time datetime="2026-09-18">2026年9月18日</time>
        </p>
      </header>

      <section>
        <h2>第一节</h2>
        <p>正文……</p>
        <figure>
          <img src="a.png" alt="描述" width="800" height="400">
          <figcaption>图 1:说明文字</figcaption>
        </figure>
      </section>

      <footer>
        <small>本文采用 CC BY 4.0 协议</small>
      </footer>
    </article>

    <aside aria-label="相关内容">
      <h2>相关阅读</h2>
    </aside>
  </main>

  <footer>
    <small>© 2026 前端开发技巧</small>
  </footer>
</body>

语义化最迷人的地方在于它的“不公平”:它几乎不需要额外的开发成本,却能同时改善可维护性、无障碍、SEO 与机器可读性。写下 <nav> 和写下 <div class="nav"> 的键盘敲击次数差不多,但它们对屏幕阅读器用户的意义,天差地别。

所以,下一次当你把手放上键盘准备敲 div 的时候,不妨先停半秒问自己一句:“这块内容,到底是什么?”——答案往往就是那个正确的标签。