html语义化结构是无障碍访问的硬性门槛,必须严格遵循:每个页面仅一个header、main、nav等landmark;h1唯一且代表核心主题;表格需正确使用caption、th、scope;表单必须绑定label并用aria-describedby关联错误提示。

HTML语义化结构不是“加分项”,而是无障碍访问的硬性准入门槛——不按规范写,屏幕阅读器就无法建立有效导航路径,等于直接拒斥约15%的真实用户。
为什么、、
这些标签在浏览器和辅助技术中对应隐式 ARIA role(如 role="banner"、role="main"、role="navigation"),而 WCAG 和主流屏幕阅读器(NVDA、VoiceOver)明确要求每个页面中同类 landmark 只能出现一次。重复使用会破坏导航逻辑,导致用户无法通过“跳转到主内容”快捷键定位 <main></main>。
- 一个页面里出现两个
<header></header>:第二个会被忽略或降级为普通容器,logo 和主导航可能被拆散读取 - 嵌套使用
<nav></nav>(比如侧边栏里再套一个<nav></nav>):除非加aria-label明确区分用途,否则屏幕阅读器会混淆“主导航”和“相关链接导航” -
<main></main>被包裹在<section></section>或<div> 里:隐式 role 丢失,键盘用户无法用 <code>Ctrl+Alt+O(NVDA)或Cmd+Option+J(VoiceOver)直达主体内容标题层级断裂比没写语义标签更危险
屏幕阅读器用户重度依赖
h1–h6的嵌套关系快速扫描内容结构。跳过层级(如h1后直接h4)或滥用h1(全页多个h1)会导致“内容地图”失效,用户被迫逐行听读。- 每个页面严格只用一个
h1,代表页面核心主题(不是 logo 文本,也不是 banner 标题) -
h2必须是h1的直接子逻辑块,h3是h2的子块——不能靠 CSS 改变视觉大小来“假装”层级 - 用
aria-level强行修正标题级别是无效的:它不改变 DOM 结构,屏幕阅读器仍按实际标签解析 - 动态渲染内容(如 SPA 路由切换)后未重置标题层级,会导致新页面的
h1被忽略或与旧标题堆叠
表格语义缺失会让数据彻底“失语”
仅靠视觉对齐的
<table> 对屏幕阅读器而言就是一串无关联的单元格。缺少 <code><caption></caption>、<th> 或 scope/headers 绑定,用户根本无法理解“哪列对应哪行”,尤其在多维汇总表中。 <ul><li> <code><caption></caption>必须紧贴<table> 开始标签后,不可放在 <code><thead> 内;否则多数屏幕阅读器不识别 <li>避免仅用 <code>colspan/rowspan构建复杂表头——必须配scope="col"或显式id+headers关联 - 每个页面严格只用一个
-
summary属性已废弃,任何还在用它的代码都会被 axe、WAVE 等工具标为严重可访问性缺陷 - 嵌套表格必须确保外层
<table> 仍有独立 <code><caption></caption>,且内层表头不污染外层语义流表单控件不绑定 label 就等于没做无障碍
<label></label>不只是视觉提示,它是把输入控件和说明文本建立 DOM 级关联的唯一可靠方式。没绑定的<input>在屏幕阅读器中只会读作“编辑框”,用户完全不知道该填什么。- 优先用显式绑定:
<label for="email"></label>+<input id="email">,比包裹式更稳定(尤其对动态插入的表单项) -
placeholder不能替代<label></label>:它会在聚焦后消失,且部分屏幕阅读器默认不朗读 - 单选/复选按钮组必须用
<fieldset></fieldset>+<legend></legend>包裹,否则每个选项都被孤立读取,失去“请选择一项”的上下文 - 错误提示需通过
aria-describedby关联到对应输入框,且该描述元素必须在 DOM 中真实存在、非 display:none
真正卡住项目的往往不是“要不要加语义标签”,而是“加在哪”“加几个”“加完是否被正确识别”。DOM 结构一旦发布,修复成本远高于编码阶段的约束检查——尤其当组件库封装了非语义化模板时,问题会层层下沉、难以追溯。
- 优先用显式绑定:











