很多人入门前端,是从一个“好玩”的项目开始的。我看到热搜里躺着“html5 超级玛丽 同人复刻版”“html5 格斗游戏”“html5 马里奥”这些词,一下就懂了——HTML5 最吸引人的地方,就是能用纯网页技术做出能跑能跳、有动画、能交互的东西。但不管你是想做小游戏、交一份网页设计作业,还是打算搞一个上线就能拿得出手的产品图册网站,所有这一切的地基,都不是花哨的 Canvas 粒子特效,而是那一堆看起来平平无奇的“语义标签”。
这篇笔记,就是围绕 HTML5 语义标签写的。我会从“为什么要有语义化”讲起,一个一个拆解常用标签的适用场景,再给出一套可以直接抄的页面骨架,最后把我在实际项目里踩过的坑和排查方法整理成速查表。不管你是刚学完基础标签的新手,还是写了一段时间 div 套 div、想提升代码质量的前端开发者,这篇笔记都能让你在看完之后,立刻把页面结构写得更加专业。
1. 语义标签到底解决什么问题
1.1 先理解“语义”这两个字
所谓语义,简单说就是“这个标签自己会说话”。你写一个<div>,浏览器和同事都只知道“这里有一块区域”,至于这块区域是导航、是正文、还是广告,完全看不出来。但你要是写<nav>,任何人一看就知道“这是导航区域”;写<article>,不用注释别人也知道“这是独立的一篇文章”。
我第一次真正理解它的价值,是在帮朋友维护一个别人写的旧项目。那个项目从头部到尾部全是<div>,为了定位,每层 div 都绑着不同的 class,什么.header-wrapper、.main-left-inner、.content-box-footer,光看 class 名字根本猜不出层级关系。后来我花了两天时间,做了一个当时看来最正确的决定:把所有纯粹的布局 div 替换成语义标签。替换完再看代码,哪怕删掉所有注释,整个页面的结构也一目了然。
用一个生活化的类比:语义标签就像是给房间贴上了门牌号。没有门牌的旅馆,你只能一间一间推开门看;贴了“厨房”“洗手间”“卧室”的牌子之后,客人自己就能找到地方,服务员打扫起来也更快。网页也是一样,语义标签让浏览器、搜索引擎、屏幕阅读器和开发者,都能更快地理解页面结构。
1.2 语义化的三个直接收益
第一个收益是 SEO。搜索引擎的爬虫虽然很聪明,但本质上也是在读 HTML。它看到一个<article>包裹的内容,加权地认为这是页面主体内容;看到<nav>,就知道这里是站内导航,不太会把它当成正文来收录。我以前做过一个测试:同一个页面,用语义标签重写结构后,百度收录时对主体内容的抓取明显更干净,摘要里不会莫名其妙出现导航链接的文字。
第二个收益是可访问性。屏幕阅读器会把页面转换成一棵“可访问树”,语义标签在这棵树上会暴露 role 属性。比如<header>会带有 banner 角色,<nav>带有 navigation 角色,<main>带有 main 角色。视力障碍用户可以通过快捷键在区域间跳转,体验完全不同。全球越来越多的网站把无障碍作为合规要求,这已经不是“加分项”,而是“必做项”。
第三个收益是代码可维护性。这个收益你可能要等到项目变大、或者换人接手时才会真正感激。语义化的 HTML 自带一份“结构注释”,团队协作时沟通成本会低很多。哪怕过了半年再回头看自己写的代码,你也能在三秒内定位到页面头部、导航、主体和页脚分别在哪里改。
1.3 一个核心原则:先语义后样式
写页面的时候,我建议你记住一句话:“先想清楚每个区块是什么,再想它长什么样。”换句话说,结构上应该用语义标签表达“这个元素是什么”,CSS 再去解决“这个元素看起来怎么样”。只要把这两层分开,你的 HTML 就不会退化成 div 堆砌。
不过这里要说清楚,语义标签并不排斥 div。恰恰相反,<div>和<span>本身也是合法的无语义元素,它们的存在价值就是当“纯粹的容器”。语义标签解决的是“区块是什么”的问题,div 解决的是“区块怎么分组”的问题。比如一个卡片组件内部,可能需要一个 div 来包住图标和文字做纵向排列,这完全合理。真正的问题在于,不能用 div 代替所有本该语义化的元素,把整个页面变成一棵看不懂的树。
2. 结构化语义标签核心拆解
2.1 页面的“骨架五件套”
HTML5 里最常用、也最容易理解的结构化标签,就是下面这五个,我把它们称为“骨架五件套”:
| 标签 | 作用 | 典型使用场景 |
|---|---|---|
<header> | 页面或区块的头部区域 | 站点头部、文章头部、组件头部 |
<nav> | 导航链接区域 | 主导航、侧边栏导航、面包屑导航 |
<main> | 页面主内容区域,一个页面只允许一个 | 文章主体、页面核心区域 |
<footer> | 页面或区块的底部区域 | 站点页脚、文章版权信息、组件底部 |
<aside> | 与主体内容相关的附属信息 | 侧边栏、广告位、补充说明、相关文章 |
可能有人会问,<header>和<footer>只能出现在页面级吗?不是。它们是“可嵌套”的语义标签,意思是<article>内部也可以有属于自己的<header>和<footer>。比如一篇文章里,开头有标题、作者、发布时间,这就可以放进<article>内部的<header>;文末的标签、版权声明,可以放进<article>内部的<footer>。这在 HTML5 里是合法且推荐的写法。
使用<header>有一个比较容易忽略的细节:它可以出现在多个区块里,但如果在<header>内部再嵌套一个<header>或者<footer>,那就不合法了。还有,<header>和<h1>~<h6>不是一回事,<header>是容器,标题标签是内容,<header>里面放不放标题都行,它只管“封装头部区域”这个职责。
2.2 main 标签的边界与踩坑
<main>是最“专一”的语义标签,一个页面只能出现一个。它代表文档的主体内容,在可访问树里对应 main 角色,很多读屏软件会提供“跳到主内容”的快捷键,就是靠这个标签实现的。
这里有几个必须注意的边界:<main>不能是<header>、<nav>、<aside>、<footer>的后代,因为这些区域本身就是页面级区块,不应该被包进“主体内容”里。同时,<main>内部可以包含<article>、<section>、<div>等任何内容,但它自己不能重复出现。
我见过不少初学者把<main>当容器用,页面里写两三个<main>,这会让读屏软件无所适从。更常见的一个问题是,有人为了布局方便,把整个页面内容(包括导航和页脚)都包进<main>里,结果页脚在可访问树里被标记为主内容的一部分,语义全乱了。记住,<main>是页面内容的重心,不是整张页面的容器。
2.3 article 与 section:最容易混淆的一对
<article>和<section>是初学者最容易搞混的组合,因为它们长得太像了。
<article>表示“可以独立分发或复用的完整内容”。判断标准很简单:如果把这块内容单独拿出来,投递到另一个网站或者 RSS 订阅里,它是否仍然成立?文章当然成立,一条评论、一条微博、一个论坛帖子、一个产品卡片,也都成立。所以它们都可以用<article>包裹。
<section>表示“文档中的一个区域”,它强调主题分组,通常带一个标题。一个<article>里可以按章节分成多个<section>,每个<section>有自己的标题,组成一个完整的逻辑链。比如一篇教程文章,可以按“环境准备”“核心概念”“实操步骤”分成三个<section>,这就是典型的用法。
选型时我给自己定了一条决策链:先看这个内容能不能独立分发,能 →<article>;不能独立,但属于某个主题区域,且需要标题 →<section>;只是一个纯粹的分组容器,没有任何语义 →<div>。这条决策链帮我解决了很多纠结的时刻。
还要特别提醒一点:<section>的规范里写明了“通常应该包含一个标题”。如果你的 section 里一个标题都放不下,那它大概率就不该用<section>,直接用<div>更合适。这是很多校验工具会提示警告的地方,也是代码质量审查里的常见问题。
2.4 页面级 aside 与区块级 aside
<aside>意为“侧边栏”,但它不只有“页面侧边栏”一种形态。规范里的定义是:与周围内容仅间接相关的部分。放到实际场景里,它可以是页面级的侧边栏(推荐文章、广告、标签云),也可以是一篇文章正文旁边的“名词解释”“引用说明”。
页面级<aside>一般直接放在<main>外部,与主体内容并列,在典型的三栏布局里占据左侧或右侧。区块级<aside>则可以放在<article>内部,辅助解释正文中的某个概念。用的时候要自问一句:这块内容拿掉之后,主体内容还完整吗?如果完整,那它的确可以算作 aside;如果不完整,那它应该是正文的一部分,而不是附属信息。
还有一个需要注意的点:<aside>并没有让浏览器自动把它“推到侧面”的能力。它的本职是表达语义,真正实现“靠左或靠右”的视觉效果,需要搭配 CSS 的 Flexbox 或 Grid。新手的误区是以为加了<aside>就会出现侧边栏布局,实际上布局是 CSS 的事,别把它俩搞混。
3. 文本级语义标签与进阶标签
3.1 旧标签的新用法:strong、em、small
很多人以为语义标签只跟页面结构有关,其实文本级的语义标签同样重要,它们在搜索引擎和读屏软件的眼里,也会有对应的强调权重。
<strong>表示“内容的重要性”,在可访问树里对应 strong 角色,读屏软件会用不同的语调读出它。<em>表示“内容的强调”,它表示语气上的着重,读屏软件的朗读方式也会不一样。值得说的是,它们默认的加粗和斜体效果只是“附带产物”,不是本质。如果你只是想文字加粗、不想表达“重要”语义,用 CSS 的 font-weight 更合适;只是想文字倾斜,用 CSS 的 font-style 更合适。
<small>也是一个容易被误解的标签。HTML5 里重新定义了它:表示“附属细则”,比如免责声明、版权声明、法律限制等,不一定指的是“小号字体”。所以页面底部那行小小的“Copyright © 2025”,用它来包裹就很贴切。
我写博客排版的时候,一度纠结要不要把文章关键词都用<strong>包起来。后来想明白了:语义标签不是用来做视觉强调的,如果把整篇文章都标成 strong,那和“全篇都是重点”等于“没有重点”是一个道理。真正重要的、需要读屏特别提示的关键词,才值得使用 strong 或 em。
3.2 时间标签 time 的超能力
<time>是一个看着不起眼、但在 SEO 和机器可读性上有大用处的标签。它允许你用datetime属性给一个人类可读的时间配上机器可读的格式,例如:
<time datetime="2025-06-01">2025年6月1日</time> <time datetime="2025-06-01T14:30:00">下午两点半</time>有人会问,明明页面上已经写了时间文字,为什么还要加一个 datetime 属性?因为人和机器对“时间”的理解不一样。人能从“明天下午三点”里读出时间,但搜索引擎和浏览器不能。有了 datetime 属性,机器才能精确地解析出这个时间是 2025-06-01T14:30:00,这在结构化数据、事件日历、搜索结果展示里非常有用。
使用 time 标签时,不要把它包裹在没有任何时间语义的文本上。比如“这篇文章写了 3 小时”,这里的“3 小时”不是一个可被日历化的具体时刻,用它就不太合适。反过来,凡是文章发布时间、活动开始时间、上下班时间点,都值得用 time 包一层。它不会影响视觉呈现,但对机器的友好度提升是实打实的。
3.3 嵌入内容标签:figure 与 figcaption
<figure>用来包裹独立的、自包含的内容单元,最常见的就是图片、图表、代码片段、音频或视频。它和<figcaption>配套使用,后者提供图文标题或说明文字。这个组合的语义是“图和说明是一体的”,而不是“图片下面随便写一句话”。
对比一下,旧式写法是:
<p><img src="chart.png" alt="柱状图"></p> <p class="caption">图1:年度销售趋势</p>这种写法的最大问题是“图”和“图的说明”在语义上没有任何绑定关系,读到后面一段的人才明白前面那张图是干嘛的。用 figure 包裹后:
<figure> <img src="chart.png" alt="柱状图" /> <figcaption>图1:年度销售趋势</figcaption> </figure>这样,图片和它的说明就成了一家人。图片加载失败、屏幕阅读器解析时,说明文字都会和图片紧密关联。这在小游戏开发、产品图册网站里尤其好用——很多产品展示页的“产品图 + 产品名称 + 一句话卖点”,用<figure>+<figcaption>来做,语义质量比一张<img>加一个<p>高出不少。
3.4 进阶标签 address、details、mark
<address>的字面意思是“地址”,但它不是用来写你家收货地址的。它表示“联系信息”,包括文档作者或组织的联系方式,比如邮箱、电话、社交账号。放在<footer>里很合适,但不要用它去包装一个旅游景点的地理坐标。如果页面上要展示公司实体经营地址,那实际上属于“文章内容”,用<p>更合适,而不是<address>。
<details>和<summary>是实现“折叠面板”的原生方案。<summary>显示标题,点击后展开<details>里的内容。这个标签很实用,尤其适合 FAQ 区块、隐私政策里的折叠说明。想知道当前是否展开,还可以用 open 属性控制默认状态:
<details> <summary>为什么推荐语义标签?</summary> <p>因为它让机器和人都能更好地理解页面结构。</p> </details>用 details 做折叠菜单,可以不写任何 JavaScript,浏览器原生支持,手机端体验也不错。
<mark>表示“标记高亮”。它不是强调,也不表示重要性,而是表示“与当前上下文相关的部分”。比如搜索结果里,用户输入的关键词可以用 mark 高亮显示,这个语义比用一个<span style="background: yellow">要精确得多。
4. 实操:搭建一个语义化的完整页面骨架
4.1 “语义化优先”的五步设计流程
动手写代码之前,我强烈建议你先建立一套设计流程,而不是打开编辑器就堆标签。我自己的流程是:
第一步,用线框图或文字列出一个页面的所有区块。比如一个博客首页,可能有:站点头部、主导航、文章列表、侧边栏(热门文章、标签)、页脚。第二步,给每个区块定义一个“语义身份”,问自己是独立文章、导航、附属内容,还是纯容器。第三步,确定嵌套层级,形成一棵语义树。第四步,再填充具体文本内容和图片。第五步,最后才写 CSS,让视觉样式服从于语义结构。
这个流程看起来慢,实际上比你边写边改要快得多。因为语义树一旦定型,CSS 的布局方案也基本定型了。而且当你把区块职责说清楚之后,会自然避免“为了对齐而多包一层 div”的设计债。
4.2 完整示例:一份可直接参考的页面骨架
下面是一份博客文章页的语义化骨架,可以直接拿去改:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>语义标签学习笔记</title> </head> <body> <header> <a href="/" class="logo">前端小站</a> <nav> <ul> <li><a href="/html">HTML</a></li> <li><a href="/css">CSS</a></li> <li><a href="/js">JavaScript</a></li> </ul> </nav> </header> <main> <article> <header> <h1>HTML5 语义标签学习笔记</h1> <p>发布时间:<time datetime="2025-06-01">2025年6月1日</time></p> </header> <section> <h2>为什么需要语义化</h2> <p>这一段是正文。</p> </section> <section> <h2>常用语义标签</h2> <p>这一段是正文。</p> <figure> <img src="semantic-tree.png" alt="语义标签层级树状图" /> <figcaption>图:常见语义标签的层级关系</figcaption> </figure> </section> <footer> <p>标签:<a href="/tags/html">HTML</a>、<a href="/tags/semantic">语义化</a></p> </footer> </article> <aside> <h2>热门文章</h2> <ul> <li><a href="#">CSS Grid 布局入门</a></li> <li><a href="#">Flexbox 完全指南</a></li> </ul> </aside> </main> <footer> <p>© 2025 前端小站</p> <address> 联系邮箱:<a href="mailto:hello@example.com">hello@example.com</a> </address> </footer> </body> </html>注意上面示例里的几个细节。<article>内部的<header>是文章头部,页面的<header>是站点头部,它们互不冲突。<main>同时包含了文章主体和侧边栏,因为侧边栏是“这个页面主体内容的一部分”,它有 aside 的语义身份,但没有跑到 main 外面去。页面底部的<footer>是整个文档的页脚,它可以包含<address>的联系信息。
4.3 给 HTML5 游戏和作品集页面加上语义结构
咱们回到开头提到的那几个热搜词:超级玛丽同人复刻版、格斗游戏、产品图册网站。这些项目的 HTML 结构其实比普通页面更适合用语义标签,原因很简单:一个游戏页面里通常包含“标题屏”“游戏画布区”“操作说明”“排行榜”这些相互独立的部分,用语义标签可以把它们的职责分得清清楚楚。
比如一个 HTML5 格斗游戏的页面骨架:
<header> <h1>街头格斗 HTML5 版</h1> <nav> <ul> <li><a href="#game">开始游戏</a></li> <li><a href="#help">操作说明</a></li> <li><a href="#rank">排行榜</a></li> </ul> </nav> </header> <main> <section id="game"> <h2>游戏区域</h2> <canvas id="gameCanvas" width="800" height="450"></canvas> </section> <section id="help"> <h2>操作说明</h2> <p>方向键控制移动,空格键攻击。</p> </section> <aside id="rank"> <h2>排行榜</h2> <ol> <li>玩家A:12000分</li> <li>玩家B:9800分</li> </ol> </aside> </main>你再想想,如果整个页面只有一大坨 div 和 canvas,游戏逻辑以后要扩展、要给 canvas 加“全屏模式”或“重新开始”按钮,代码得多难维护。语义化结构最大的价值,就是让你后续迭代时能快速找到“排行榜该改哪里”“操作说明该加在哪里”。
产品图册网站也是同理。每个产品卡片可以用<article>作为容器,内部配<figure>包裹产品图,再用<h3>写产品名。这样搜索引擎抓取页面时,能清晰识别出“一个产品条目”的完整信息,比一堆无法分辨边界的列表项强太多了。
4.4 语义树自查:写完结构后做的事
一个页面写完,我会花几十秒做一次“语义树自查”。方法很简单:把页面里的 sectioning 标签(body、header、nav、article、section、aside、footer)全部列出来,看它们的嵌套关系是不是一棵干净的树。如果发现某个 section 没有标题,某个 header 出现在不该出现的位置,或者 main 出现了两次,就说明结构有问题。
这种自查不需要什么高级工具,浏览器开发者工具的 Elements 面板里,把标签层级展开看一眼就够了。养成这个习惯之后,你写出来的页面即使不经过 Lighthouse 检测,也知道基本能拿高分。
5. 常见问题、验证工具与经验避坑
5.1 高频问题速查表
我在带新人、审代码时,见过太多重复踩坑的情况,整理成一张表:
| 问题 | 错误示例 | 正确做法 | 原因 |
|---|---|---|---|
| main 出现多次 | 页面里有 2 个<main> | 一个页面只保留一个<main> | 读屏软件依赖 main 定位主内容,多个 main 会让快捷键失效 |
| article 套 article 乱嵌套 | 把产品列表整体包在 article 里,每个产品也包在 article 里 | 外层用<section>,内层产品用<article> | 列表本身不是独立条目,列表内的产品才可独立分发 |
| section 没有标题 | <section><p>内容</p></section> | 要么补一个标题,要么改用<div> | section 规范要求有标题表示主题 |
| header 内嵌 header | <header><header>站点名</header></header> | 合并或拆开 | header 没有嵌套语义,属于结构错误 |
| 用 div 做导航 | <div class="nav"><a>链接</a></div> | 用<nav>包裹 | div 不暴露 navigation 角色,读屏无法识别导航区域 |
| time 包没有时间语义的文本 | <time>3小时</time> | 用<span>或<p> | time 只能表示日期、时间点或持续时间,不能表达“3小时前”这种描述 |
| address 放普通地址 | <address>北京市朝阳区某街道</address> | 用<p>等普通标签 | address 只表示作者/组织的联系方式 |
这张表你可以收藏下来,写页面的时候对照着扫一遍,能避掉大部分语义化初期的低级错误。
5.2 怎么验证你的语义标签写对了
判断语义标签写没写对,不能光靠肉眼“看起来合理”,要用工具验证。
第一个工具是 W3C 的 HTML 校验器(validator.w3.org/nu)。直接把页面 URL 或者代码贴进去,它会告诉你标签嵌套是否合法、section 是否缺少标题、哪些属性写错了。这个工具没有玄学,报错信息就是规范的标准解释,值得认真读。
第二个工具是浏览器 Lighthouse。打开 Chrome 开发者工具,切到 Lighthouse 面板,生成一份报告,里面“Accessibility”和“SEO”两项会直接反映出语义化的问题。尤其值得关注的是“Document doesn't have a main landmark”这类提示,它说明你的页面里缺少<main>,或者<main>使用不当。
第三个工具是浏览器的 accessibility tree。在 DevTools 的 Elements 面板里,可以切到“Accessibility Tree”试图,查看读屏软件实际能“看到”的树。如果这里呈现出来的结构清晰、有明确的 banner/navigation/main/contentinfo 角色,基本就说明语义对了。
5.3 语义标签与 ARIA 的正确关系
提到可访问性,就绕不开 ARIA(Accessible Rich Internet Applications)。ARIA 允许你给元素手动添加 role 属性,弥补原生语义的不足。但这里有一条铁律:能用原生 HTML 语义标签的,就不要用 ARIA。
为什么?因为原生语义标签自带的行为、键盘交互和角色映射,是经过浏览器多年打磨的,比自己手工造的 role 可靠得多。比如一个导航,用<nav>比<div role="navigation">更地道。一个按钮,用<button>比<div role="button" tabindex="0" @click="...">更不容易出问题。
ARIA 真正发挥价值的场景,是构建自定义组件的时候。比如一个手风琴菜单,原生没有现成的“手风琴”标签,你需要组合 details/summary,或者用自定义实现并配合 aria-expanded、aria-controls 这些属性,把展开状态告诉读屏软件。这个话题可以单独写一篇,但结论很简单:ARIA 是用来补位的,不是用来替代语义标签的。
5.4 兼容性:语义标签在旧浏览器的表现
HTML5 语义标签在现代浏览器里没有任何兼容性问题,但如果你需要兼容 IE8 及更早版本,就得注意了。那些老浏览器不认识<header>、<nav>这些标签,会把它们当成未知内联元素,导致没有默认的 display: block 样式,布局直接崩掉。
常规的兼容方案是用 HTML5shiv,在页面 head 里引入一段脚本,让旧浏览器把新标签识别为块级元素。另外还需要在 CSS 里给这些标签加上display: block:
header, nav, main, article, section, aside, footer { display: block; }不过到今天,做新项目时,我建议直接放弃对 IE 的兼容。原因很现实:IE 的市场份额已经非常低,维护兼容的成本远大于收益。如果你的项目还硬性要求兼容老 IE,那说明业务本身需要重新评估技术选型了。
5.5 经验心得:从“用对”到“用好”
最后分享几条我自己的经验心得,每一条都是实操换来的。
第一条,搞不清标签就用“内容自问法”。问自己:这段内容独立拿出去还能成立吗?能,用 article。这段内容属于某个主题但需要标题统领?用 section。这段内容只是布局分组,没有任何主题含义?用 div。把这个问题问完,大部分标签选择都有了答案。
第二条,不要为了语义化而语义化。见过一些项目,一个只有一句话的小组件也套上 header/main/footer 三层标签,这是过犹不及。语义标签是为内容服务的,内容本身很轻的时候,少一点嵌套反而更清晰。
第三条,语义标签要跟着内容走,不是跟着设计稿走。设计稿上两个区块长得一模一样,不等于它们在语义上也是同一种东西。一个区块是文章列表,另一个是广告位,它们就该分别用 article 和 aside,哪怕视觉上一模一样。反过来,两个长得完全不同的区块,只要语义身份相同,也可以都用 article 包裹。
第四条,多用.visually-hidden技术,而不是滥用语义标签做“看不见的内容”。如果你确实需要提供一个供读屏用户阅读、但视觉上不显示的标题,常见做法是加一个 CSS class,把它定位为视觉隐藏,而不是偷偷塞一个不可见的 h1。这属于无障碍细节,但很能体现专业度。
写 HTML 和写文章一样,好的结构让人读起来行云流水,坏的结构让人改一次骂一次。语义标签不是什么高深的技术,它真正考验的是你对内容的理解、对用户的同理心,以及写代码时的耐心。希望你从这篇笔记里拿到的,不只是标签清单,更是一套“先想清楚再动手”的思维方式。