长页面必须用地标区域而非仅靠heading层级,因为heading只提供内容层级,不定义功能角色;地标是辅助技术的导航骨架,支持快捷键直接跳转,而无名称的地标或dom顺序错乱会严重阻碍用户操作。

为什么长页面必须用地标区域而不是只靠 heading 层级
只靠 <h1></h1> 到 <h6></h6> 无法让屏幕阅读器用户快速跳转到“主要内容”或“侧边栏”,因为 heading 只提供层级,不提供功能角色。地标(landmark)是辅助技术的导航骨架,用户能用快捷键(如 NVDA 的 D 键、VoiceOver 的 Ctrl+Option+U)直接列出所有地标并跳转——这对动辄滚动十几屏的长页面是刚需。
常见错误是把整个 <div class="sidebar"> 加 <code>role="complementary",却没配 aria-label 或 aria-labelledby,结果屏幕阅读器只读“complementary”,用户完全不知道这是“热门文章推荐”还是“广告位”。
-
<main></main>必须且只能出现一次,且应包裹真正核心内容(不是整个) - 多个
<nav></nav>是允许的(如顶部主导航 + 文章内目录),但每个都应有aria-label区分,比如aria-label="文章目录" - 避免无意义地标:一个纯装饰性
<div role="region"> 会污染地标列表,反而降低导航效率<h3>如何给非语义化区块打上正确地标角色</h3> <p>当 legacy 代码或 CMS 输出一堆 <code><div> 时,不能硬套 <code><aside></aside>或<section></section>,得用 ARIA 补语义。关键不是“加了 role 就行”,而是角色必须匹配实际用途,且提供可理解的名称。例如一个由 JS 动态插入的“实时评论区”,原生没有对应标签:
<div id="live-comments" role="region" aria-labelledby="comments-heading"> <h2 id="comments-heading">最新评论</h2> <!-- 评论列表 --> </div>
这里
role="region"是合理选择(因为不是标准地标,又需被识别为独立区域),但必须搭配aria-labelledby指向真实标题;若用aria-label="实时评论"也行,但优先复用可见文本,避免维护两套文案。-
role="search"只用于真正的搜索功能,不要给筛选控件乱贴 -
role="application"极其危险,除非你真实现了完整键盘交互模型(如富文本编辑器),否则禁用 - 旧版 IE 不支持隐式地标(如
<nav></nav>自带role="navigation"),但现代浏览器和所有主流屏幕阅读器都支持,无需降级补role
长页面中地标顺序与 DOM 结构的隐藏陷阱
屏幕阅读器按 DOM 顺序读取地标,但视觉布局常靠 CSS(
flex/grid/position: absolute)打乱顺序。如果 DOM 中<aside></aside>写在<main></main>前面,即使它显示在右侧,屏幕阅读器也会先读侧边栏再读正文——用户可能直接迷失。解决方法不是靠
aria-flowto(支持度差),而是确保 DOM 顺序符合逻辑流:主内容优先,辅助区域后置。必要时用display: contents或调整模板渲染顺序。- 用
outline: 2px solid red临时检查视觉顺序 vs DOM 顺序是否一致 -
<main></main>里嵌套的<section></section>不自动成为地标,除非显式加role="region"并命名,否则只是语义分组 - 动态加载内容(如无限滚动)插入新区块时,若未赋予地标角色,新内容不会出现在地标列表中,用户无法感知
测试地标是否真起作用的三个实操动作
写完地标别只看 Lighthouse 报告——它只检测标签存在,不验证行为。必须手动用真实工具验证:
- NVDA + Firefox:按
D键打开地标列表,确认每个条目名称可理解(不是 “region 1”、“banner” 这种默认名) - VoiceOver + Safari:三指双击呼出 rotor,选 “Landmarks”,滑动听读,检查是否遗漏关键区域(如漏掉
<footer></footer>) - 用 axe 浏览器插件运行,重点看
landmark-one-main、landmark-unique等规则,但注意它不会报aria-label是否准确,得人工听
最易被忽略的是:地标名称必须稳定。如果用 JS 动态改
aria-label(比如从 “加载中…” 变成 “已加载 5 条”),要同步触发aria-live="polite",否则屏幕阅读器可能卡在旧名称上。 -











