构建无障碍组件之Landmarks Pattern

作者:anOnion日期:2026/9/7

Landmarks Pattern 详解:页面地标区域的无障碍实现

Landmarks(地标)是一组用于标识页面主要区域的 ARIA 角色。本文基于 W3C WAI-ARIA Landmarks PatternLandmark Regions Practice,详解 8 种地标角色、HTML 原生元素映射与最佳实践。

一、Landmarks 的定义与核心概念

1.1 什么是 Landmarks

Landmarks 是 8 个 ARIA 角色的集合,用于标识页面的主要结构区域。每个地标角色让辅助技术用户感知到一段高级页面结构的起始和结束——这种结构在视觉上通常通过布局位置、间距、颜色或边框来传达。

例如,main 地标标识页面主要内容区域,navigation 地标标识导航链接区域。

Landmarks 的核心价值:

  • 结构感知:将视觉布局转化为程序化语义
  • 键盘跳转:屏幕阅读器提供在地标之间快速跳转的快捷键
  • 跳转目标:可作为“跳过导航”链接的目标
  • 浏览器扩展:浏览器扩展可利用地标增强键盘导航

1.2 核心术语

术语说明
Landmark标识页面主要区域的 ARIA 角色
Top-level顶层地标,不应嵌套在其他地标内
Label地标的可访问名称,同角色多次出现时必须唯一
1┌─────────────────────────────────────────────────────────────┐
2  banner                                                     
3    Logo | Site Search | Login                               
4├─────────────────────────────────────────────────────────────┤
5  navigation (Site)            complementary (Related)       
6    Home            |            Recent Posts                
7    Blog            |            Tags                        
8    About           |            Archive                     
9    Contact         |                                        
10├─────────────────────────────────────────────────────────────┤
11  main                                                       
12    Article Title                                            
13    Article content...                                       
14                                                             
15    search (Site Search)                                     
16    [ Search... ]  [ Go ]                                    
17├─────────────────────────────────────────────────────────────┤
18  contentinfo                                                
19    Copyright | Privacy | Accessibility Statement            
20└─────────────────────────────────────────────────────────────┘
21

1.3 八种地标角色概览

角色HTML 元素说明顶层
bannerheader站点级头部(Logo、搜索)
mainmain页面主内容
navigationnav导航链接组-
searchsearch搜索功能区域-
complementaryaside补充内容(侧边栏)
formform表单区域(非搜索、非主内容)-
regionsection可命名的独立内容区域-
contentinfofooter站点级页脚(版权、声明)

1.4 HTML 元素 vs ARIA 角色

许多 HTML 元素自动创建地标区域,无需额外添加 ARIA 角色:

HTML 元素隐含地标角色
header(上下文为 body 时)banner
footer(上下文为 body 时)contentinfo
mainmain
navnavigation
asidecomplementary
searchsearch
form(有可访问名称时)form
section(有可访问名称时)region

推荐:优先使用 HTML 原生元素,只有在元素无法使用或需要覆盖隐含角色时才添加 ARIA role 属性。

1.5 典型应用场景

  • 博客页面banner(头部导航)+ main(正文)+ complementary(侧边栏)+ contentinfo(页脚)
  • 搜索页面search(搜索框)+ main(搜索结果)+ navigation(分页)
  • 仪表盘main(主面板)+ 多个 region(图表区域)+ navigation(侧边菜单)
  • 文档站banner(站点头部)+ navigation(目录)+ main(文档内容)+ complementary(相关链接)

二、八种地标角色详解

2.1 Banner

标识站点级头部内容,通常包含 Logo、站点标识和站点搜索。

  • 每个页面最多一个 banner 地标
  • 应为顶层地标
  • HTML header 元素在 body 上下文中自动创建 banner

注意header 作为 articleasidemainnavsection 的子元素时,不会创建 banner 地标。

2.2 Main

标识页面的主要内容区域。

  • 每个页面应有一个 main 地标
  • 应为顶层地标
  • HTML main 元素自动创建 main 地标

2.3 Navigation

标识导航链接组,用于站点或页面内容导航。

  • 如果页面有多个 navigation 地标,每个必须提供唯一标签
  • 如果两个 navigation 包含相同的链接集,应使用相同的标签
  • HTML nav 元素自动创建 navigation 地标

2.4 Search

标识搜索功能区域。

  • 当表单用于搜索功能时,应使用 search 而非 form 地标
  • HTML search 元素自动创建 search 地标(较新的 HTML 元素)

2.5 Complementary

标识补充内容区域,设计为与主内容互补,但脱离主内容后仍有意义。

  • 应为顶层地标
  • 如果补充内容与主内容不相关,应使用更通用的 region 角色
  • HTML aside 元素自动创建 complementary 地标

2.6 Form

标识表单区域,当没有其他命名的地标(如 mainsearch)更合适时使用。

  • form 地标应有标签以帮助用户理解表单用途
  • 标签应对所有用户可见(如 h1-h6 标题元素)
  • HTML form 元素有可访问名称时自动创建 form 地标

注意:不要用 role="form" 标识每个表单字段,它标识的是整个表单区域。

2.7 Region

标识包含重要内容的可感知区域,用户需要能够导航到该区域。

  • region 地标必须有标签
  • 可用于标识其他命名地标无法恰当描述的内容
  • HTML section 元素有可访问名称时自动创建 region 地标

2.8 Contentinfo

标识站点级页脚信息,通常包含版权、隐私声明和无障碍声明链接。

  • 每个页面最多一个 contentinfo 地标
  • 应为顶层地标
  • HTML footer 元素在 body 上下文中自动创建 contentinfo

注意footer 作为 articleasidemainnavsection 的子元素时,不会创建 contentinfo 地标。

三、设计原则

3.1 第一步:识别逻辑结构

将页面拆分为可感知的内容区域——设计师通常通过对齐和间距来视觉化表达这些区域。区域可进一步拆分为逻辑子区域。

3.2 第二步:分配地标角色

根据每个区域的内容类型分配地标角色:

  • bannermaincomplementarycontentinfo 应为顶层地标
  • 地标角色可以嵌套以表达父子关系
  • 模态对话框内容不需要包裹在地标区域内——模态打开时地标无法提供额外价值,模态本身已提供名称和边界

3.3 第三步:为区域命名

  • 如果同一地标角色在页面上出现多次,每个实例必须提供唯一标签
  • 唯一的例外:多个实例的内容和用途完全相同(如表格上下两套相同的分页控件)
  • 如果区域以标题元素开头,用 aria-labelledby 引用该标题作为标签
  • 如果区域需要标签但没有标题,用 aria-label 提供
  • 不要在标签中包含角色名——navigation 地标标为“Site Navigation”会被读成“Site Navigation Navigation”,应简化为“Site”

四、实现方式

4.1 HTML 原生元素方式(推荐)

1<body>
2  <header>
3    <h1>My Site</h1>
4    <search>
5      <label for="q">Search</label>
6      <input id="q" type="search" />
7      <button>Go</button>
8    </search>
9  </header>
10
11  <nav aria-label="Main">
12    <ul>
13      <li><a href="/">Home</a></li>
14      <li><a href="/blog">Blog</a></li>
15      <li><a href="/about">About</a></li>
16    </ul>
17  </nav>
18
19  <main>
20    <article>
21      <h2>Article Title</h2>
22      <p>Article content...</p>
23    </article>
24
25    <aside aria-labelledby="related-heading">
26      <h2 id="related-heading">Related Links</h2>
27      <ul>
28        <li><a href="/post/1">Previous Post</a></li>
29        <li><a href="/post/2">Next Post</a></li>
30      </ul>
31    </aside>
32  </main>
33
34  <footer>
35    <p>&copy; 2026 My Site. <a href="/privacy">Privacy</a></p>
36  </footer>
37</body>
38

4.2 ARIA 角色方式

当无法使用 HTML 原生元素时,用 role 属性覆盖:

1<div role="banner">
2  <h1>My Site</h1>
3</div>
4
5<div role="navigation" aria-label="Main">
6  <ul>
7    <li><a href="/">Home</a></li>
8  </ul>
9</div>
10
11<div role="main">
12  <article>...</article>
13</div>
14
15<div role="contentinfo">
16  <p>&copy; 2026 My Site.</p>
17</div>
18

4.3 命名方式

1<!-- 方式一:aria-labelledby 引用可见标题(推荐) -->
2<nav aria-labelledby="main-nav-heading">
3  <h2 id="main-nav-heading">Main Navigation</h2>
4  <ul>...</ul>
5</nav>
6
7<!-- 方式二:aria-label 提供不可见标签 -->
8<nav aria-label="Footer">
9  <ul>...</ul>
10</nav>
11
12<!--  错误:标签包含角色名 -->
13<nav aria-label="Footer Navigation">
14  <!-- 会被读成 "Footer Navigation Navigation" -->
15</nav>
16

4.4 多实例命名

1<!-- 多个 navigation 必须唯一命名 -->
2<nav aria-label="Main">
3  <ul>...</ul>
4</nav>
5
6<nav aria-label="Footer">
7  <ul>...</ul>
8</nav>
9
10<nav aria-label="Breadcrumbs">
11  <ol>...</ol>
12</nav>
13

五、最佳实践与常见错误

5.1 所有内容必须在地标内

1<!--  错误:内容游离在地标之外 -->
2<body>
3  <main>
4    <p>主内容...</p>
5  </main>
6  <div>
7    <p>这段内容不在任何地标内</p>
8  </div>
9</body>
10
11<!--  正确:所有内容包裹在地标内 -->
12<body>
13  <main>
14    <p>主内容...</p>
15    <section aria-labelledby="extra-heading">
16      <h2 id="extra-heading">补充内容</h2>
17      <p>这段内容在 region 地标内</p>
18    </section>
19  </main>
20</body>
21

5.2 地标数量不超过 7 个

地标的价值随着数量增长而递减。经验法则:每页 7 个或更少的地标区域。如果超过,考虑用 region 合并相关内容,或重新评估页面结构。

5.3 重复角色未命名

1<!--  错误:两个 navigation 无标签,屏幕阅读器无法区分 -->
2<nav>
3  <ul>...</ul>
4</nav>
5<nav>
6  <ul>...</ul>
7</nav>
8
9<!--  正确:每个 navigation 有唯一标签 -->
10<nav aria-label="Main">
11  <ul>...</ul>
12</nav>
13<nav aria-label="Breadcrumbs">
14  <ol>...</ol>
15</nav>
16

5.4 标签包含角色名

1<!--  错误:标签含角色名 -->
2<nav aria-label="Site Navigation">
3  <!-- 读成 "Site Navigation Navigation" -->
4</nav>
5<aside aria-label="Complementary Section">
6  <!-- 读成 "Complementary Section Complementary" -->
7</aside>
8
9<!--  正确:标签不含角色名 -->
10<nav aria-label="Site">
11  <!-- 读成 "Site Navigation" -->
12</nav>
13<aside aria-label="Related Posts">
14  <!-- 读成 "Related Posts Complementary" -->
15</aside>
16

5.5 header/footer 上下文问题

1<!-- header/footer  body 直接子元素时创建地标 -->
2<body>
3  <header><!-- banner 地标 --></header>
4  <main><!-- main 地标 --></main>
5  <footer><!-- contentinfo 地标 --></footer>
6</body>
7
8<!--  header/footer 嵌套在 article/section 内时不创建地标 -->
9<article>
10  <header><!-- 不是 banner 地标,只是语义化标签 --></header>
11</article>
12

5.6 模态对话框不需要包裹地标

1<!--  不必要:模态内容包裹在 region  -->
2<div role="dialog" aria-modal="true" aria-labelledby="dlg-title">
3  <div role="region" aria-label="Dialog Content">
4    <h2 id="dlg-title">Confirm</h2>
5    <p>Are you sure?</p>
6  </div>
7</div>
8
9<!--  正确:模态本身就是容器,提供名称和边界 -->
10<div role="dialog" aria-modal="true" aria-labelledby="dlg-title">
11  <h2 id="dlg-title">Confirm</h2>
12  <p>Are you sure?</p>
13</div>
14

5.7 form vs search 混用

1<!--  错误:搜索表单用 form 地标 -->
2<form role="form" aria-label="Search">
3  <input type="search" />
4  <button>Search</button>
5</form>
6
7<!--  正确:搜索表单用 search 地标 -->
8<search>
9  <input type="search" />
10  <button>Search</button>
11</search>
12
13<!--  正确:非搜索表单用 form 地标 -->
14<form aria-labelledby="contact-heading">
15  <h2 id="contact-heading">Contact Us</h2>
16  <!-- 表单字段 -->
17</form>
18

六、Agentic 时代 Landmarks 的定位

“无招胜有招。” —— 金庸《笑傲江湖》

风清扬向令狐冲传授独孤九剑时,点破的正是这五个字。Landmarks 也正如此理——没有复杂的键盘交互、没有状态管理,只有 8 个角色和几条规则。但它的价值在 Agentic 时代反而会提升:

  1. AI 生成 UI 需要结构化语义——当 LLM 生成页面时,正确使用地标角色能确保生成的 UI 对所有用户可访问
  2. 自动化测试的地标验证——无障碍审计工具(如 axe-core)以地标作为页面结构完整性的基础检查项
  3. AI Agent 的页面导航——浏览器自动化 Agent 可利用地标快速定位内容区域,而非依赖 CSS 选择器

越是花哨复杂的技术(CSS 选择器、视觉定位)越容易随样式重构而失效;Landmarks 这种返璞归真的结构化语义,反而是 AI Agent 与自动化工具最可靠的“锚点”。它不需要任何 JavaScript,是成本最低、收益最高的无障碍实践——只需用正确的 HTML 元素,就能让整个页面对辅助技术开放。

七、总结

构建无障碍的 Landmarks 需要关注:

  1. 优先原生 HTMLheadermainnavasidesearchfooterformsection 自动创建地标。
  2. 全量覆盖:所有页面内容必须包裹在某个地标内,不遗漏任何可感知内容。
  3. 控制在 7 个以内:地标价值随数量递减,超过 7 个考虑合并或重构。
  4. 唯一命名:同角色多实例时必须提供唯一标签,优先用 aria-labelledby 引用可见标题。
  5. 标签不含角色名aria-label="Site" 而非 aria-label="Site Navigation"
  6. 顶层原则bannermaincomplementarycontentinfo 应为顶层地标,不嵌套在其他地标内。
  7. 模态例外:模态对话框内容不需要包裹地标,模态本身已提供容器语义。

遵循 W3C Landmarks Pattern 规范,用最小的成本实现最大的无障碍收益。

文章同步于 an-Onion 的 Github。码字不易,欢迎点赞。


构建无障碍组件之Landmarks Pattern》 是转载文章,点击查看原文


相关推荐


Python能做嵌入式开发吗?一份写给动手派的生态与硬件全景图
卷无止境2026/8/30

很多人对Python的印象还停留在"跑在电脑或服务器里的脚本语言",写爬虫、搞数据分析、训练模型都行,但嵌入式这种要直接操控芯片、控制引脚电平的活儿,好像轮不到它。这个印象放在十年前基本没错,但放在今天,已经过时了。 答案是能,而且做得相当漂亮。只不过这里有个关键前提,得先搞清楚你说的Python到底是哪一种。嵌入式世界里,Python早就不是单一身份,而是分裂出了几条并行的技术路线,各自服务不同的硬件层级和使用场景。 Python在嵌入式里的三副面孔 先给一张全景图,理清脑子里的概念层级。


Rust where详解:让泛型与Trait约束更加清晰
程序员爱钓鱼2026/8/22

《Rust编程实战》系列第48篇 上一篇文章中,我们学习了Rust泛型Generics,知道可以通过: fn show<T>(value: T) { } 让同一套代码适配不同类型。但泛型本身只表示“类型可以变化”,如果代码需要这个类型具备某种能力,就必须配合Trait Bound。例如: use std::fmt::Display; fn show<T: Display>(value: T) { println!("{}", value); } 这里: T: Display 表示


一行代码没写,我用AI做了一个可以收费产品
大侠Luffy2026/8/9

这是我第一次尝试完全依靠 AI 编程工具,从零到一开发一款产品。整个过程中,我自己一行代码都没有写。 转写模型使用的是 Qwen ASR,GPU 算力来自 Vast.ai。Vast.ai 的消费级 GPU 虽然价格便宜,但想把它做成稳定、可靠的在线服务,并不是一件容易的事。 为了兼顾成本与服务稳定性,我借助 Codex 放弃了官方的 Serverless 方案,从零构建了一套 GPU 实例调度系统。 这个过程中踩了很多坑,但非常值得。如果没有 AI 的帮助,我估计至少需要 3 个月才能把这件事


DeepResearchSystem 0x04:MAS 进阶
chaors2026/7/31

回顾 DeepRearchSystem 0x00:初识 DeepRearchSystem 0x01:Agent 基础 DeepRearchSystem 0x02:Graph 构建 DeepRearchSystem 已经具备了 HITL 机制。现在可以说是基本链路已经跑通了,那我们还能做哪些优化呢? 之前我是做移动端开发的,在写代码之前的设计总会提前考虑到一些编程的设计模式和原则,像六大设计原则: 单一职责原则 开闭原则 里氏替换原则 接口隔离 依赖倒置 迪米特法则 这里感觉 单一职责 和


PP-OCR Linux 部署不再折腾:OpenCV、ONNX Runtime、OpenVINO 三版本开箱即用
天天代码码天天2026/7/23

目录 一套接口,三个 Linux 推理版本 不只是完整 OCR,也支持“只识别” 自带浏览器测试页面 解压后即可启动 支持 API Key,但不把密钥打印到日志 可以安装为 systemd 服务 ONNX Runtime 的 CPU 与 CUDA OpenVINO 版不需要目标机器安装 SDK 不同开发语言如何接入? v1.3.0 做了哪些验证? 下载与交流 做 OCR 项目时,真正让人头疼的往往不只是“能不能识别”,而是后面的部署问题: C++、C#、Pyt


为什么 MCP、Skill、RAG 能工作?从 Conversation Loop 看现代 Agent 的底层架构
吴佳浩Alben2026/7/15

《为什么 MCP、Skill、RAG 能工作?从 Conversation Loop 看现代 Agent 的底层架构》 作 者:吴佳浩Alben 撰稿时间:2026.7.10 更新时间:2026.7.13 前言 很多文章介绍 Agent 时,都会分别讲 MCP、Skill、Function Calling、RAG,却很少回答一个更关键的问题: MCP、Skill、RAG 为什么能够协同工作?它们究竟是如何融入 Agent 的? 答案,其实都藏在 Agent 的执行主线——Convers


当 Linux 成为“空气”:容器、Agent 与不再重要的“桌面之争” -- 肘子的 Swift 周报 #143
东坡肘子2026/7/7

当 Linux 成为“空气”:容器、Agent 与不再重要的“桌面之争” 一周前,微软推出了无需 Docker 的 Windows 11 原生容器支持的公开预览;再结合苹果不久前发布的容器管理器(container)1.0 正式版,一时间,两大主流桌面操作系统都将 Linux 容器深度集成为了系统的一等公民。 这件事引发了一场有趣的讨论。有人认为,这是 Linux 的最终胜利:虽然它始终没能真正赢下桌面市场,但它已经无处不在;也有人提出反问:当 Windows 和 macOS 都能相对顺畅、轻量


让 AI Agent 系统自己发现 bug、自己提修复 PR:自我进化的 Harness
谭sir2026/6/29

本文介绍怎么让 AI Agent 的工程代码(Harness)具备自我进化能力——自动记录运行数据、自动识别错误模式、自动生成修复 PR(Pull Request,合并请求)。内容覆盖监控、错误模式识别、自动修复、行为分析和生产落地方案,每一章都会配合 demo 项目 evo-agent-demo 的代码和运行结果来讲解。 从一个 bug 说起 假设你做了一个 AI Agent 产品,它可以搜索资料、查数据库、执行代码。上线前也在内部进行了反复测试,并且没发现什么问题,于是就正式上线了。 但产品


【节点】[Rectangle节点]原理解析与实际应用
SmalBox2026/6/20

【Unity Shader Graph 使用与特效实现】专栏-直达 Rectangle 节点是 Unity URP Shader Graph 中一个功能强大的形状生成工具,专门用于在着色器中创建矩形图案。该节点基于输入的 UV 坐标生成矩形形状,通过精确的参数控制可以实现从简单的方形到复杂矩形图案的创建。在游戏开发中,矩形形状有着广泛的应用场景,包括创建 UI 元素、制作平铺纹理、构建几何图案、实现遮罩效果等。 该节点的核心工作原理是通过数学计算在 UV 空间中定义矩形区域。UV 空间是一个标


LangChain上手 MCP:从用别人工具到自己写工具
颜酱2026/6/12

LangChain上手 MCP:从用别人工具到自己写工具 读完这篇文章,你将能:用别人的 MCP Server(比如百度地图)、自己写一个 MCP Server(比如计算器)、用 Agent 自动调度多个工具。 先看效果 用别人的:百度地图 MCP 十几行代码,让 AI 变成地图助手: import asyncio from langchain_mcp_adapters.client import MultiServerMCPClient from langchain.agents imp

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读