先说个我在代码评审里最常见的画面:一个页面从上到下几十个 <div>,class 名一层套一层,从 box 到 wrap 到 container,内层再套 item、info-item、item-info,改一个样式得顺着 DOM 找半天,浏览器 read 模式直接抽风,SEO 也不太行。这种写法的本质不是懒,而是没搞明白 HTML 里的语义化标签到底解决什么问题。我见过太多前端新手(甚至工作两三年的)卡在这一步:知道 <article>、<section>、<nav> 这些标签存在,但真写页面时还是条件反射式地敲 <div>。
这篇内容就是我基于日常培训和代码评审经验整理的第一篇 HTML 精讲。我会先讲清楚语义化到底在解决什么,再逐个拆解文档结构、文本语义、表单场景里最值得用的标签,最后给你一套可以直接照着自查的检查方法。适合刚学完 HTML 基础、想往进阶走的前端新手,也适合写了两年代码但一直靠 div 糊页面的朋友。
1. 全是 div 的页面到底错在哪:三个真实代价
1.1 语义化不是“换标签好看”,而是给机器看的地图
HTML 从设计之初就不是给人看的,是给“程序”看的。浏览器、搜索引擎爬虫、读屏软件、抓取工具,它们拿到 HTML 之后要根据标签推断页面结构。你写的每一个标签,都相当于在告诉这些程序:这一块是什么、重要程度如何、和周围内容是什么关系。
设想一个场景:你用 <div class="nav"> 写导航,再用 <div class="footer"> 写底部。浏览器渲染出来没问题,人眼看起来也没问题,但搜索引擎爬虫在没有 CSS 的情况下是“看”不到 class 名称含义的。它只知道满屏都是无语义的通用容器,无法判断哪些是主要内容、哪些是页脚。结果是页面主要内容被忽略,权重被稀释,排名自然受影响。我参与过的几个项目里,只做了一件事——把 header/main/footer/nav 语义化标签替换掉原有 div 结构,不做任何内容改动,站点收录量和关键词排名在两三个月内都有明显提升。
读屏软件更依赖这些标签。视障用户使用屏幕阅读器时,会通过快捷键在 landmark(地标)之间跳转,比如直接跳到导航、跳到主要内容。如果页面全是 div,读屏软件只能从头到尾朗读,用户可能连续听了十几秒广告和装饰性内容才听到正文。这不是体验问题,是可用性问题。
1.2 结构可读性差是团队协作的第一杀手
我在评审代码时感受最深的一点是:语义化标签写得好的人,他的 HTML 结构本身就是文档。你不需要看注释,不需要看 CSS 类名,扫一眼就知道这个页面哪些是头部、哪些是导航、哪段是正文、哪个是页脚。
反过来,纯 div 结构意味着你必须从 class="content-left-2" 这类命名中去猜测设计意图,而 class 命名是团队里最容易产生分歧的地方。有人管侧边栏叫 sidebar,有人叫 aside-panel,有人叫 right-col,一个页面改版三次,class 名能变出五个版本。如果你从结构层就用 <aside> 标识侧边栏,无论 class 怎么改,语义不会变。
换句话说,语义化标签是团队协作里的“共同语言”,它绕开了命名分歧,直接表达结构意图。前端架构师或高级开发者在指导新人时,检查 HTML 结构是否语义化,比检查 CSS 更高效。
1.3 维护成本:样式变化时,div 的脆弱性就暴露了
前端页面永远逃不过改版。div 布局最尴尬的问题在于:它的视觉位置和语义毫无关联,一旦布局调整,某个 div 在整个页面中的结构层级可能完全移位,但它的标签还是 div,你无法从标签本身快速得知它该挪到哪里。
举个例子:移动端网页常见“底部导航”——三个 tab:首页、分类、个人中心。你用三个 div 做,也许第一次搭建时很开心,但下次加收藏页、加购物车,点击区域、结构嵌套越来越多,你会发现整个可点击区域的代码根本没法维护。如果你用 <nav> 加 <ul><li><a> 的语义结构,每个入口都是列表项 + 链接,增加入口就是在列表里加一项,结构天然稳定。
所以我的结论很直接:div 可以用,但只应该用在“实在没有合适语义标签”的场景——比如纯粹的样式容器、JS 挂载点、布局用的栅格。它不是原罪,滥用才是原罪。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从 div 到语义化第一道分水岭:五大文档结构标签
2.1 header / footer:不只是“页头”和“页脚”
新手最容易犯的错是把 <header> 和 <footer> 理解成“页面顶部区域”和“页面底部区域”。实际上这两个标签是局部作用域的概念,不是全局概念。
<header> 表示的是一段介绍性内容的容器,它可以出现在页面级别,也可以出现在 article、section 内。比如一篇文章的标题、作者、发布时间,就可以放在 <article> 内部的 <header> 里,表示“这篇文章的头部信息”,而不是页面页头。同样,<footer> 放在 article 内部可以作为文章尾部信息区,放版权、相关链接、标签等,不必局限于页面最底端。
我在项目里常用的写法:
html复制<header class="site-header">
<a href="/" class="logo">MySite</a>
</header>
<article>
<header>
<h1>前端语义化标签实战</h1>
<p class="meta">发布日期:2026-01-10</p>
</header>
<p>正文内容……</p>
<footer>
<p>标签:<a href="#">前端</a>、<a href="#">HTML</a></p>
</footer>
</article>
判断标准就一句话:这段内容是不是所在区块的“开端/收尾”?如果是,就该用 header/footer,而不是随便套 div。
2.2 nav:不只给顶部导航用
<nav> 专门用于页面中的主导航链接块。很多开发者只在页面顶部写个 <nav> 就觉得完成了导航语义化,这是个很常见的误解。页面中所有主要导航链接集合都应该用 <nav> 标记,包括侧边栏的“相关文章”列表、底部的“友情链接”区块,都可以使用 <nav>。只要它是一组独立的导航链接,而不是段落内部的散落链接。
一个实用经验:如果你的导航块很大,且包含二级菜单、下钻列表,标准做法是在 <nav> 里加 <ul><li><a> 的层次结构。这样既保持了语义嵌套,读屏软件也能按列表逐项读取,用户体验会好很多。而且用 <ul> 包导航有个额外好处:视觉上天然对齐、间距可控,后续加 hover 效果也方便。
html复制<nav aria-label="主导航">
<ul>
<li><a href="/">首页</a></li>
<li><a href="/blog">博客</a></li>
<li><a href="/about">关于我</a></li>
</ul>
</nav>
一个容易踩坑的细节:<nav> 只标记“主要导航”,页面里出现十几个 <nav> 反而会稀释它的语义权重。想标记次要链接组时,用 <footer> 内部的普通链接列表就足够了,不需要额外套一个 nav。
2.3 main:一个页面只能有一个主角
每个页面只允许有一个 <main>,它代表页面的主要内容区。搜索引擎和读屏工具拿到页面后会优先定位 <main>,直接跳转到这里读取核心内容。所以它应该直接包含本页最有价值的内容,比如文章正文、产品详情、搜索结果列表。不要把侧边栏、广告位、版权信息放进去。
常见错误是一个页面出现多个 <main>,或者 <main> 里套 <div> 导致内容被层层包裹。规范要求 <main> 是顶级结构,不能被 <article>、<aside> 这些区块元素嵌套(虽然浏览器不报错,但语义模型会乱)。正确做法是页面结构像这样:
html复制<body>
<header>……</header>
<main>
<article>文章内容</article>
</main>
<footer>……</footer>
</body>
写单页应用(SPA)时尤其要注意:Vue 或 React 切换路由只替换内容区,保持 <main> 标签在根模板里唯一存在即可。很多框架组件在 DOM 重建时可能会产生重复的 <main>,代码评审时我一般会顺手查一遍。
2.4 article 与 section:最容易被搞混的一组
这两个标签的关系和区别,是前端面试题里反复出现的考点。官方文档里的定义是:<article> 表示文档中独立的、可分发或复用的结构,例如一篇帖子、一篇文章、一则评论;<section> 表示一个通用章节,它通常包含一个标题,表示文档中的一个主题分组。
我的通俗理解是:能独立成篇的内容用 article,不能独立、只是某篇内容的一个章节,用 section。博客首页的卡片列表,每张卡片都是独立的一篇文章,应该用 article;一篇教程里的“环境准备”“代码实现”“踩坑总结”三个部分,各部分单独提出去都不完整,需要组合在一起才是一篇文章,用 section。
面试时我一般建议这样答:article 强调独立性,section 强调主题分组;article 里可以有多个 section,section 内部就不该再嵌一个完整的 article(虽然技术上允许,但语义上是矛盾的)。代码示例:
html复制<article>
<h2>HTML语义化标签实践指南</h2>
<section>
<h3>为什么需要语义化</h3>
<p>……</p>
</section>
<section>
<h3>常用标签详解</h3>
<p>……</p>
</section>
</article>
这样即使没有 CSS,内容层次依然是:文章 → 章节 → 正文,清晰明了。
2.5 aside:不只是“侧边栏”
很多前端初学者觉得 <aside> 就是侧边栏,这是完全不够的。<aside> 表示与周围内容“间接相关”的内容,比如文章正文旁边的作者介绍、文章末尾的“推荐阅读”、页面侧边的广告位,都是“间接相关”。但你如果写的侧边栏里放的是“本周热门文章排行榜”,这个排行榜只对站内运营有价值,跟当前文章关系不大,用 <aside> 标记也合理。核心判断标准是“关联性”:和当前内容直接相关 → 不放 aside;只有间接关系 → 可以考虑 aside。
实践中我经常把“相关文章”“标签云”这两个栏目放在文章正文末尾的 <aside> 里,这样 SEO 爬虫在读取正文后能顺着 aside 的关键词发现更多相关内容,也能略微增加页面关键词密度。
3. 正文里真正高频的语义化标签:不止标题和段落
3.1 h1-h6:层级不能乱
这个不需要多解释,但很多新手会踩坑:页面标题用了 h1,文章卡片里的标题又用 h1,导致一个页面五个 h1。正确做法是每个页面只保留一个 h1,一般对应页面核心内容或网站名称。之后根据视觉层级依次使用 h2 到 h6,且不要让标题级别跳级,比如 h1 后直接 h4,会让读屏软件和爬虫很难判断层级关系。
有基础的前端应该明白,h1-h6 的语义不是“字号由大到小”,而是“内容重要性从高到低”。视觉字号是 CSS 干的事。所以当你觉得“这个 h2 的字太大了”,应该改 CSS 而不是换标签。
3.2 列表三兄弟:ul、ol、dl 各有分工
<ul> 是无序列表,<ol> 是有序列表。导航、功能列表、标签云用 ul;操作步骤、排行榜用 ol。<dl> 是描述列表,用来描述“键值对”,例如简历里的“姓名:张三”“技能:前端开发”,定义术语和说明。很多前端写“属性-值”结构时硬用 div + span,其实 dl 天然就是干这个的。
我特别喜欢用 <dl> 的场景是商品参数页:手机的长度、宽度、重量、屏幕尺寸。用 dl 结构写出来,爬虫能识别这是属性描述,而且天然的键值对排版非常方便:
html复制<dl>
<dt>屏幕尺寸</dt>
<dd>6.7英寸</dd>
<dt>重量</dt>
<dd>203克</dd>
</dl>
这里有个容易被忽略的点:<dt> 和 <dd> 可以有一对多关系,一个词条可以对应多个描述。例如“支持网络”对应“5G”“4G”“Wi-Fi 6”,这种语义关系用 dl 表达最准确。
3.3 figure 与 figcaption:图片和注释的官方搭配
文章里插一张图,图下面写“图 1:效果对比”,这是最常见的需求。很多人的写法是:
html复制<div class="img-wrap">
<img src="chart.jpg" alt="性能对比图">
<p>图 1:优化前后性能对比</p>
</div>
问题在于,这个 <p> 本质上是对图片的说明,不是正文段落。更好的语义化写法是:
html复制<figure>
<img src="chart.jpg" alt="性能对比图">
<figcaption>图 1:优化前后性能对比</figcaption>
</figure>
<figure> 标记独立的内容单元(图片、代码块、图表等),<figcaption> 是对它的文字说明。这个结构在浏览器 read 模式、PDF 导出、爬虫解析时都会得到更好的处理。代码块也适用,我写技术博文时常用 figure 包裹代码块再加一个说明性的 figcaption,视觉上比简单放一个 <pre> 更规整。
3.4 time 与 datetime:时间信息别裸奔
页面上到处是时间:文章发布时间、更新时间、活动截止日期、评论时间。如果直接输出 2026年1月10日,爬虫和读屏软件只知道这是文本,不知道这是一个日期。想要让程序“看懂”时间,就得用 <time> 标签配合 datetime 属性:
html复制<time datetime="2026-01-10">2026年1月10日</time>
datetime 属性值必须是机器可读格式,例如 2026-01-10 或 2026-01-10T14:30:00+08:00。这样既能展示人类友好的文字,程序也能拿到标准格式。Vue/React 项目里可以从接口返回的时间戳,渲染时用 dayjs 格式化后再输出到 time 标签,这不难做,但很多人不知道有这个标签。
3.5 强调类标签:strong、em、b、i 的取舍
这组标签是中文博客里最容易被写错的一组。先说结论:
<strong>表示内容重要性,默认粗体。<em>表示语气强调,默认斜体。<b>表示“阅读时需要注意但不具更高重要性”的文本,如文章摘要中的关键词。<i>表示“区别于正文文本”的内容,如专业术语、外语单词、表情图标,默认斜体。
很多教程直接说“b 和 i 只是纯样式,不要用”,这个说法不够准确。HTML5 规范已经重新定义了 b 和 i 的语义:b 用于“不提高重要性的注意文本”,i 用于“表现出不同语气或语态的文本”。例如文章里的“Photoshop”这种软件名、诗句里的外文,用 i 是合理的。
实战中的取舍标准是:如果我要强调一句话,选择 strong;如果只是觉得这段文字视觉上应该粗一点,那应该用 CSS 的 font-weight,而不是用 b 硬顶。不要为了视觉加粗而使用强调类标签,这是语义和样式分离的基础。
4. 表单与交互区域:语义化最容易翻车的地方
4.1 label 与 input:可点击区域和读屏的桥梁
表单是大多数前端项目里交互最密集的地方,也是语义化最容易翻车的地方。新手最常见的做法是:
html复制<input type="text" placeholder="请输入用户名">
如果这个输入框没有对应的 <label>,读屏软件读到这里只会说“编辑框”,用户完全不知道要填什么。视觉上 placeholder 有提示,但读屏软件对 placeholder 的读取支持并不一致,对视力障碍用户非常不友好。
正确写法:
html复制<label for="username">用户名</label>
<input type="text" id="username" name="username">
用 for 指向 input 的 id 建立关联。一个实用技巧:点击 label 文字时,对应输入框会自动聚焦,你可以不用 label 只靠 placeholder 吗?可以,但移动端点击区域太小,容易误触。用 label 后点击区域扩大,体验会好很多。
实在不想让 label 显示在页面上时,可以用 CSS 隐藏但要保留在 DOM 中(不要用 display: none),让读屏仍然能识别:
html复制<label for="search" class="sr-only">搜索</label>
<input type="search" id="search" placeholder="请输入关键词">
.sr-only 的经典实现是移动到屏幕外,而不是直接隐藏,这一点前端项目里建议直接封装成一个工具类复用。
4.2 fieldset 与 legend:给表单分组
注册页里常常有“基本信息”“账号设置”两个区块;问卷里常常有“单选题”“多选题”分区。语义化做法是为每组字段包一层 <fieldset>,然后用 <legend> 给这组字段起个标题:
html复制<fieldset>
<legend>账号信息</legend>
<label for="username">用户名</label>
<input type="text" id="username">
<label for="password">密码</label>
<input type="password" id="password">
</fieldset>
<fieldset>
<legend>个人资料</legend>
<label for="nickname">昵称</label>
<input type="text" id="nickname">
</fieldset>
读屏软件会读“账号信息 用户名 编辑框”,用户很清楚当前在填写什么分组。而且 fieldset 默认有边框,如果你不需要边框,用 CSS 重置边框即可,不要因为嫌默认样式丑就不用它。
性别选择这一块有个常见问题:三个 radio 按钮要不要包 fieldset?我的建议是包,因为“性别”这个分组本身有一条 legend 说明分组含义,读屏会更友好。
4.3 按钮与链接:button、a、div 三者的语义红线
前端项目里最惊人的滥用是“用 div 做按钮”。比如:
html复制<div class="btn" onclick="handleClick()">提交</div>
这个写法三个问题:一是键盘用户无法用 Tab 聚焦到这个“按钮”,按回车/空格也不会触发;二是读屏软件不会把它识别为按钮,只会读“提交”文本,用户不知道可以点击;三是不带 role="button" 时,自动化测试也无法正确模拟点击。
语义正确是:
html复制<button type="button" onclick="handleClick()">提交</button>
如果确实因为设计稿不能用原生 button 样式,那就用 CSS 重置 button 样式,而不是换 div。另一个高频场景是“整块卡片可点击”。新手会把整块内容包在一个 <a> 里,但这样会破坏内部文字结构。更好的做法是在卡片上用 <a> 包标题,再通过 CSS 伪元素把整个卡片铺成链接区域。这样既保留 a 标签的语义,也恢复大点击区域。
4.4 输入类型与 inputmode:移动端键盘的隐形语义
<input> 的 type 属性是几乎不用解释的语义化。type="email" 会触发移动端邮箱键盘,type="tel" 会触发数字拨号键盘。很多人只记得 type="text" 和 type="password",对 tel、email、url、number、search 这类语义化输入类型不够敏感。移动端用户感受非常直接:手机号输入框如果不写 type="tel",调用的是全键盘而不是数字键盘,用户输入体验会差很多。
更现代的补充是 inputmode 属性,它可以让 type="text" 的输入框在移动端也弹出数字键盘。例如验证码输入框:
html复制<input type="text" inputmode="numeric" pattern="[0-9]*" maxlength="6" autocomplete="one-time-code">
这里 autocomplete="one-time-code" 还能让 iOS 自动识别短信验证码,这是个很实用的小技巧,很多项目没有配置,白白浪费系统能力。
5. 别指望记住所有标签:一套能落地的自查方法
5.1 三步自查:去掉 CSS 后页面还读得通吗
这一套方法是我在做代码评审和带队培训时反复用的,非常简单。写完之后养成三个习惯:
第一步,关掉 CSS。 浏览器里直接禁用样式,看页面结构能不能读懂。如果文字顺序混乱、层级不清、链接之间没有分隔,说明内容包裹层次有问题。语义化好的页面,无 CSS 状态仍然是一篇“干净的纯文本文档”。
第二步,查看标题大纲。 有些浏览器插件可以生成页面大纲,或者直接看 DOM 里 h1-h6 的层级。正常来说是“页面标题 h1 → 区块标题 h2 → 子标题 h3” 的树状结构。如果出现“h1 → h4 → h2”,说明标题层级乱了。SEO 工具如 Lighthouse 里也有 heading 结构检查项,Lighthouse 访问页面就能看到。
第三步,用键盘走一遍。 用 Tab 键从页面顶部开始,能不能按顺序聚焦到所有链接和按钮?有没有元素聚焦但没有任何可见焦点样式?焦点顺序是否符合阅读顺序?这是无障碍的基础测试,也是语义结构是否合理的试金石。
5.2 不是所有 div 都要替换:哪些场景 div 是对的
我反复强调,不要陷入“看到 div 就浑身难受”的洁癖。以下场景用 div 完全合理:
- 纯样式容器:比如一个弹性布局的包裹层,
<div class="flex-row">用于摆放子元素,它本身没有语义。 - JS 挂载点:Vue 的
#app、React 的#root,这是应用入口,用 div 合理。 - 装饰元素:做背景色块、分割线、遮罩层,不需要语义。
- 图标字体容器:如果你用
<i>塞图标字体,我会建议改成<span class="icon">,但如果你已经写了<i>,也别过度恐慌,注意区分实际场景即可。
我自己的判断标准是:去掉这个标签后,内容结构是否受影响? 如果只是视觉变化,用 div 没问题;如果内容分层、含义、可访问性会受影响,就应该用语义化标签。
5.3 团队提效:把语义化写进代码规范
如果你在带前端小组,或者想跟同事统一标准,可以考虑在代码规范里加一条简单的语义化检查项。我常用的做法是:
- 所有导航链接必须用
<nav><ul><li><a>。 - 所有表单输入必须关联 label。
- 所有按钮必须用
<button>,禁止 div 模拟。 - 页面上只能有一个 h1。
- 新增组件时必须先在文档里想清楚用哪个语义标签,写不了再上 div。
- 代码评审时用 Lighthouse 跑一次 Accessibility 和 SEO,低于 90 分的 PR 打回去。
这些规则不需要把所有标签都列出来,抓最重要的几条就行。我在团队里推了一年多,效果是新人上手速度明显加快,他们看老代码时不容易迷路,因为脚手架里已经铺好了语义骨架。说起来有点反直觉,加了这些“限制”之后,前端开发的速度反而更快了,因为你不必每次都在 class 命名上想半天,结构的位置和角色是固定的,剩下的工作就是往里填内容。
5.4 从会写标签到会挑标签:一个日常训练法
最后分享一个我在培训学员时常用的练习,非常简单但很有效:每周挑两个自己日常访问的网站,用开发者工具查看它们的 HTML 结构,不用细看 CSS,只看它们是怎么选择语义标签的。你会发现,很多头部站点在结构层级上出奇一致:header 包 logo 和 nav,main 里放核心内容,article 包文章,aside 放次要内容,footer 放版权。看多了之后,你再拿到一张设计稿,大脑会自动把每个区块映射到合适的标签上,这个能力叫“结构直觉”。
如果你时间比较多,还可以做这个进阶练习:拿一份纯 div 写的老页面,尝试把每个 div 替换成最合适的语义标签,但不改动任何 CSS 类名和布局逻辑。做完之后查看 Lighthouse 评分变化和 DOM 结构的可读性差异,你会对“标签即文档结构”这句话有特别直观的感受。
我第一次做这种练习时,最惊讶的是对 <section> 的处理。很多区块看着“好像应该是个 section”,但仔细想又会觉得“这个区块不够独立,是不是不该用 section”。后来我总结了一套判断:先看有没有标题,没有标题的容器大概率是普通 div,因为 section 规范要求包含一个标题。先问“这个区块有标题吗”,再问“能独立出去吗”,一个问题就能解决大半困惑。
这套方法和所有技术一样,不练永远只是知道,练完才会真正变成肌肉记忆。
