屏幕阅读器依赖语义标签(如、、)定位页面区域,否则只能线性读取所有文本;须包裹导航链接集合,全页唯一且不可嵌套,标题须严格递进,和等标签提供机器可解析元数据,aria是补救而非替代。

屏幕阅读器依赖语义标签定位页面区域
没有语义化标签时,屏幕阅读器只能线性读取所有文本节点,用户得听完整页才能判断哪块是导航、哪块是正文。加上 <nav></nav>、<main></main>、<aside></aside> 这类标签后,辅助工具能直接提供“跳转到导航”“跳转到主要内容”等快捷指令。
关键点在于:这些标签本身不带样式,但浏览器和读屏软件会内置对应的行为逻辑。比如按 Insert + N(NVDA)或 Ctrl + Option + U(VoiceOver)就能唤出区域列表。
-
<nav></nav>必须包裹一组导航链接,不能只包一个<a></a> -
<main></main>在整个页面中只能出现一次,且不能嵌套在<article></article>或<section></section>内部 -
<aside></aside>不等于“右边栏”,它表示与主内容相关但可独立存在的旁注,比如术语解释、作者简介
标题层级错乱会让读屏用户彻底迷失上下文
很多开发者用 <h2></h2> 开头,中间穿插 <h1></h1>,再回到 <h3></h3> —— 这种跳跃会让屏幕阅读器把结构理解成“子章节突然变成父章节”,用户无法建立稳定的认知地图。
正确做法是严格遵循视觉与逻辑一致的层级:每个 <section></section> 或 <article></article> 应该以 <h2></h2> 起始(如果整体页面已用 <h1></h1>),内部子模块用 <h3></h3>,依此类推。跳级不是 bug,是可访问性阻断点。
- 禁止在
<header></header>外单独使用<h1></h1>,除非它是页面唯一主标题 - 用 CSS 隐藏标题但保留语义(如
position: absolute; clip: rect(1px, 1px, 1px, 1px);)比用<div> + 字体加粗更安全 <li>动态渲染的标题(如 React 中通过 props 注入)必须确保 DOM 插入时层级连续</li> <h3>时间、联系、地址等信息必须用对应语义标签包裹</h3> <p>单纯写“2025-09-12”或“北京市朝阳区XXX路”对人可读,但对辅助技术只是普通字符串。加上 <code><time datetime="2025-09-12"></time>或<address></address>,读屏软件就能识别类型并调整语调、提供快捷操作(如长按复制日期、唤起地图 App)。这类标签的 value 不是装饰,而是机器可解析的元数据。搜索引擎同样依赖它们提取结构化信息,比如富摘要(Rich Snippets)就要求
<time></time>的datetime属性格式合法。-
<time></time>的datetime值必须符合 ISO 8601(如2025-09-12T14:30),不能是中文格式“2025年9月12日” -
<address></address>只适用于最近的<article></article>或级别的联系信息,不是所有“地址”都要套它 -
<figure></figure>+<figcaption></figcaption>是图片/图表的最小语义单元,缺失<figcaption></figcaption>会导致读屏软件跳过说明
aria-label 和 role 不是语义化替代方案
看到
<div role="navigation"> 就该警觉:这说明本该用 <code><nav></nav>却没用。WAI-ARIA 是补丁,不是基建。原生语义标签自带隐式 ARIA role 和键盘行为(比如<button></button>自带role="button"和空格/回车触发),而手动加role很可能漏掉 focusable、keyboard interaction 等配套逻辑。真实项目里最常踩的坑,是以为加了
aria-label就解决了可访问性——其实它只覆盖 label 缺失场景,解决不了结构混乱、焦点流断裂、状态同步失败这些底层问题。- 优先用
<nav></nav>而非<div role="navigation"> <li> <code>aria-label仅用于无文本内容的控件(如纯 icon 按钮),有可见文本时用<label for></label>或<fieldset></fieldset>更可靠 - 动态更新的内容(如加载中提示)必须配合
aria-live,但前提是容器本身已有正确语义(如<status></status>或<region></region>)
实际项目中最容易被忽略的,是语义标签和 JavaScript 交互的耦合点:比如用 JS 切换
<main></main>的hidden属性,却不更新其父级的aria-hidden,结果读屏用户还在读已被隐藏的内容。语义不是写完就完的事,它得活在每次 DOM 更新里。 -











