ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

前端无障碍核心:ARIA Landmarks地标模式原理与实战

2026/9/18 17:53:45 拓冰建站 浏览量
前端无障碍核心:ARIA Landmarks地标模式原理与实战 一直以来我都觉得前端圈聊“无障碍”聊得最多的是alt文本、label关联、颜色对比度这些点。这些东西当然重要但它们更像是“单点修补”。真正让页面从一个“能读的文档”变成“可高效操作的界面”的往往是信息架构层面的无障碍设计。而 Landmarks Pattern就是这层设计里最基础、最容易被忽略、但收益最稳定的一块。简单说Landmarks Pattern 就是给页面划分“地标区域”。视觉用户打开一个网站扫一眼就能分清顶部导航、左侧边栏、正文区、底部的版权信息然后迅速把目光聚焦到自己想看的位置。但屏幕阅读器用户没有这种“扫一眼”的能力他们是线性听取内容的。如果页面结构没有语义化他们只能从头到尾把整页听一遍才能大概知道页面上有什么。Landmarks Pattern 要解决的就是“让读屏用户也能像视觉用户一样快速定位到页面的核心区域”。这篇文章会从 WAI-ARIA 的地标角色讲起结合真实的页面改造案例把每个角色怎么选、怎么用、坑在哪里说到位。适合刚接触无障碍的前端开发者也适合那些已经做了 basic 无障碍适配但总觉得差点意思的团队。这不是一篇纯理论科普我会把能直接抄的代码和验证方法都放出来。1. 先搞清楚 Landmarks Pattern 到底在解决什么问题1.1 从一次真实的“读屏体验”说起我在一次无障碍测试中用 NVDA 打开了一个未做 landmarks 的资讯网站首页。读屏朗读的顺序是页头 logo → 搜索框 → 一组 banner 链接 → 登录/注册 → 主菜单第一项 → 第二项…… 一直往下大概读了 40 多秒才进入正文。这期间我作为一个有视觉经验的测试者已经很不耐烦了。真实用户每天要在这种场景下操作几十次那种挫败感是难以想象的。而加上 landmarks 之后读屏用户只需要按下一个快捷键NVDA 的d键或者通过“地标列表”对话框就能瞬间跳转到“主导航”“主内容”“页脚”这些区域。整个浏览逻辑从“线性被迫听完”变成了“按需检索”。这才是 Landmarks Pattern 的核心价值它不是给页面“加功能”而是给读屏用户提供“视觉用户天生就有”的空间定位能力。1.2 ARIA Landmarks 的设计初衷Landmarks Pattern 出自 WAI-ARIA 规范的地标Landmark章节。它的设计思路非常朴素把页面常见的区域抽象成一组可枚举的角色比如banner、navigation、main、complementary、contentinfo、region、search、form然后把这组角色暴露给辅助技术Assistive TechnologyAT。辅助技术收集到这些角色后就可以为读屏用户生成一个“页面区域索引”用户可以通过这个索引快速跳转。这里的关键是“可枚举”。视觉设计是无限的但交互区域的需求是有限的。用户使用页面时核心诉求无非就是这里是什么区域、这个区域是干什么的、我怎么过去。Landmarks 把这个诉求标准化了。这也是为什么它被纳入 WAI-ARIA 规范后几乎被所有主流读屏和浏览器一致支持。相比某些前沿但兼容性差的 ARIA 特性Landmarks 是“投入产出比”极高的实践。1.3 视觉设计与读屏导航之间差了一整层语义很多同行会问我用了 HTML5 的header、nav、main、footer标签是不是就自动有了 landmarks答案是不完全是。HTML5 语义化是大方向但有一个关键误区HTML5 的区段语义和 ARIA 的地标语义不是一一对应的。比如header在作为article或section的子元素时并不产生banner地标只有作为body的直接子元素或主要顶级结构时它才隐性映射为banner。再比如section如果没有可访问名称accessible name它不会暴露为region地标加了名称才会。所以Landmarks Pattern 并不是“给每个标签打角色”那么简单而是需要你真正理解“地标是在描述页面层级而不是标签嵌套关系”。2. 六大核心 Landmark 角色与正确选型2.1 核心角色逐一拆解ARIA 1.2 规范中地标角色一共有 8 个banner、navigation、main、complementary、contentinfo、region、search、form。实际使用中前 6 个是绝对高频后 2 个有严格限制条件。我逐个说。banner页面级横幅通常放 logo、站点名、全局标语。一个页面建议只保留一个且必须位于顶级不被article等区块包裹。如果header在 body 顶级可以不用显式写rolebanner它会被隐式映射。navigation导航区域。页面上可以有多个但必须通过aria-label区分否则读屏会报“导航 导航”这种重复且无意义的名称。这点我见过太多团队踩坑。main主内容区一个页面只能有一个。如果 SPA 里切换路由后有多处main要用hidden或移除引用确保同一时刻只有一个可见的 main。complementary侧边栏、相关推荐、补充内容区。它的含义是“与主内容相关但可以被独立理解”。不是所有侧栏都该是 complementary纯粹的广告位、版权栏不属于这里。contentinfo页脚版权、站点信息。同banner页面级通常只能有一个且应位于顶级。region一个通用的、需要被作为“可跳转区域”凸显出来的区块。它是最灵活的角色但规范要求必须提供可访问名称否则不会暴露为地标。下表是我自己整理的选择参考角色对应英文名称核心用途页面数量限制可访问名称要求bannerBanner站点级页眉1通常不需要navigationNavigation链接分组/导航可以多个必须区分mainMain页面唯一主内容1通常不需要complementaryComplementary侧栏/补充内容可以多个建议提供contentinfoContent Info页脚/版权1通常不需要regionRegion通用独立区块建议少量必须提供searchSearch站内搜索可以多个建议提供formForm表单/搜索容器慎用必须提供2.2 容易被误用的角色form和regionform角色是一个“高危”角色。ARIA 规范里form地标用于“可收集和提交数据的表单区域”。但并不是所有的form都应该成为地标。如果页面上有大量表单登录、搜索、评论、订阅逐个暴露为地标会让读屏的地标列表变得极其臃肿。而且规范明确要求只有当表单区域足够大、足够重要时才应该使用form地标。实际项目中我倾向于大多数小表单只保留原生form加aria-label不额外声明roleform。region的误用更隐蔽。很多团队为了“把所有区块都变成可跳转区域”把页面里的卡片、列表、文章摘要区全部加上roleregion。结果是读屏用户打开地标列表看到几十个“区域”节点同样丧失了快速定位的能力。Landmarks Pattern 的精髓是“少而准”不是“全而杂”。只有那些对用户导航决策有实际意义的区块才值得成为region。2.3 真的需要把所有区块都变成 landmark 吗我理解很多团队追求“标准全覆盖”的心态但不建议这么做。Landmarks 是给用户的“目录”目录太长了就没有目录的意义。一个复杂的新闻首页合理的地标数量在 58 个之间一个营销落地页35 个足够一个后台看板系统因为功能密集最多也就 6 个左右。我通常会告诉团队一个判断标准如果一个区块被移走或隐藏用户还能正常理解页面其他部分那它就不需要成为 landmark。换句话说地标应该服务于“用户带着目的来到页面时的第一层信息检索”而不是服务于“内容归属关系的语义表达”。语义表达靠 HTML5 标签就够了地标是导航层面的东西两者不能混着用。3. 实操给页面装上 Landmarks3.1 第一步页面语义审计动手改造之前先把自己的页面当成一份“没有视觉样式”的文本然后问三个问题这个页面有几个视觉意义上的“大区”简单说就是“你看一眼页面视线会先落到哪几个明显分隔的区域”。这些大区之间是什么关系是平行关系比如侧栏和正文还是包含关系比如页头里套了个导航每个大区如果用一个词命名它是什么导航、内容、页脚、侧栏、搜索……这一步可以借助浏览器的 Accessibility Tree 来辅助完成。Chrome DevTools 的 Elements 面板里选择任意元素后可以在 Accessibility 标签页看到它暴露给辅助技术的角色和名称。这个视图比直接看代码更接近“读屏视角”非常适合审计阶段使用。3.2 第二步用 HTML 或 ARIA 补齐结构审计完就可以做改造了。我给出一个常见的“新闻类页面”改造前后的对照示例。改造前页面是这样的div classheader span classlogo某某新闻/span div classnav-menu a href/首页/a a href/news资讯/a a href/sports体育/a a href/finance财经/a /div /div div classmain-wrapper div classsidebar h2热门推荐/h2 ul.../ul /div div classcontent h1今日要闻标题/h1 p正文.../p /div /div div classfooter span版权所有/span /div这个结构在视觉上没有任何问题但读屏用户得到的全部是未命名、无层次的div盒子。改造后header classheader span classlogo某某新闻/span nav aria-label主导航 a href/首页/a a href/news资讯/a a href/sports体育/a a href/finance财经/a /nav /header div classmain-wrapper aside classsidebar aria-labelledbysidebar-heading h2 idsidebar-heading热门推荐/h2 ul.../ul /aside main classcontent h1今日要闻标题/h1 p正文.../p /main /div footer classfooter span版权所有/span /footer注意几个细节header和footer直接放在body顶级不需要显式写role它们会自动映射为banner和contentinfo。nav使用了aria-label主导航。这个命名不是可选项是必填项因为页面上只有这一个导航时虽然不命名也能用但一旦后续加了页脚导航、相关链接导航不命名的后果就是读屏报出一堆没法区分的“导航”。aside不能被自动识别为complementary必须显式加上rolecomplementary或者配合 HTML5 规范让它自然暴露。为了兼容性考虑我一般显式写role同时用aria-labelledby关联区块标题给它一个名字。main是几个角色里用得最省心的直接一个标签就能映射而且在多数读屏里都支持m快捷键直接跳转到 main 区域。3.3 第三步给地标起名字命名是 Landmarks Pattern 里最容易出问题、也最容易被忽略的环节。ARIA 规范要求多个同类地标比如多个 navigation、多个 complementary必须通过可访问名称区分。如果页面上有两个导航一个叫“主导航”一个叫“页脚导航”读屏用户才能准确选择自己要去的那个。起名的方式有两种aria-label直接给字符串或aria-labelledby引用页面上已有的标题元素。我建议优先用aria-labelledby因为它的名字会跟随实际可见标题内容。比如侧栏的标题是“热门推荐”你用aria-labelledbysidebar-heading关联后地标名称就是“热门推荐”视觉用户看到的文字和读屏用户听到的名称是一致的没有信息偏差。但也有必须用aria-label的场景当一个地标区域没有合适的可见标题时。比如顶部导航区域页面上并不会有“主导航”这几个字这时aria-label主导航就是最自然的做法。核心原则就是一个字对得上。读屏用户听到的名字必须能让他搞清楚这个区域的功能。3.4 用“地标列表”验证命名效果改造完成后验证工作不能只靠代码审查。我建议至少用一种读屏和一种浏览器组合测试一下“地标列表”功能NVDA Firefox/Chrome按Insert F7打开元素列表选择“地标”标签页就能看到当前页面的全部地标及名称。VoiceOver Safari/iOS打开“转子”找到“地标”或“区域”选项左右滑动即可在各区域间跳转。JAWS Chrome按R键可以在各个 landmark 之间循环跳转。从地标列表里你要检查三件事地标数量是否符合页面复杂度、每个地标是否有清晰名称、是否出现了重复名称或空白名称。尤其注意如果名单里出现了一堆没有名字的region那说明之前的判断有误或者忘写了aria-label/aria-labelledby。4. 常见问题与排查技巧实录4.1 高频问题速查表我整理了实际项目中被问得最多的问题和对应处理方案问题现象可能原因处理方案读屏地标列表里出现多个“横幅”页面中多个header位于顶级或使用了rolebanner保留顶级页面头一个其余区块改用region或调整嵌套层级读屏报“导航 导航”多个nav没有区分名称每个nav用aria-label或aria-labelledby命名地标列表为空使用了section但没给名称给需要暴露的section加aria-labelledby或删除多余 roleaside读不出来只用了aside未处理隐藏语义显式添加rolecomplementary并建议命名点击跳转 main 后焦点落在了错误位置页面只有一个 main但内容包含多个动态区域确保 SPA 切换页面时只有一个 main 可见并使用tabindex-1让 main 可聚焦地标数量过多盲目给所有区块加roleregion回到判断标准只保留对用户导航决策有用的区块其中“地标数量过多”这个问题我多说一句。它不影响规范符合性但严重影响体验。我在改造一个后台项目时一开始给每个卡片区块都加了region地标列表足足有 17 项。后来梳理后降到了 6 项操作效率翻了不止一倍。地标列表是给用户做快速跳转的就像网页的“目录”目录全是“子子小节”那还不如不要目录。4.2 一个真实的 SPA 跳转问题N年前做一个 Vue 后台项目按路由拆分页面每个页面模板里都写了一个main。当时没有做任何处理结果读屏地标列表里出现了多个 main。WAI-ARIA 规范明确说 main 地标在一个页面里只能出现一个多个 main 会带来歧义。这个问题最典型的解法是把所有路由页面放在同一个容器中切换路由时动态更新 main 的内容而不是各自渲染一个独立的 main。或者在非活跃页面组件中给 main 加上hidden属性确保同一时刻只有一个 main 在可访问性树中可见。后者改造成本更低但要注意hidden对页面的布局影响以及动画过渡的闪烁问题。另外如果你用 React 的createPortal渲染全局浮层也要检查浮层里有没有误加rolemain或rolebanner。这类组件通常挂在 body 顶级极易被错误识别为页面级地标。4.3 自动化检查与人工验证结合Landmarks 的源码审查靠 axe-coreaxe-core/playwright或eslint-plugin-jsx-a11y就能发现大部分问题比如重复 banner、缺失 main、未命名的 complementary 等。我在 CI 流程里加了一个简单的 Playwright 检查import { runAxe } from axe-core/playwright; await page.goto(/); const results await runAxe(page, { runOnly: [landmark-unique, landmark-no-duplicate-banner, landmark-no-duplicate-contentinfo, region-rule], }); if (results.violations.length) { console.table(results.violations.map(v ({ id: v.id, impact: v.impact }))); }但这里我必须说清楚自动化检查只能验证“有没有”验证不了“名称对不对”“数量是否合理”“顺序是否自然”。这些只能靠人工模拟读屏操作来验证。我在交付前一定会要求测试同事按三条路径走一遍打开页面 → 打开地标列表 → 逐个跳转从 banner 开始按 Tab 走完整页用快捷键直接跳到 main 再读一遍正文。三条路径都能顺畅走完才算达标。5. 这个模式后续还能怎么用从页面级到组件级Landmarks Pattern 除了应用在完整页面上在复杂组件里也在发挥越来越重要的作用。尤其是现在前端组件库盛行比如 Dialog、Tabs、Accordion 这类组件内部的区块划分如果沿用同样的地标思路读屏用户体验会有质的提升。以 Tab 组件为例很多人会给每个 Tab 面板加上roletabpanel。但这里有个细节wai-aria 的 Tabs 模式并不建议每个 tabpanel 都成为地标否则一次页面里有几十个 tabpanel用户导航也成了灾难。正确做法是仅当 Tab 面板承载的是类似“内容区”这样的核心角色时才考虑让它暴露为一个带有命名的地标比如roleregion。其余情况只需要保证 Tab 本身的键盘操作正确即可。对话框Dialog组件同理。打开一个弹窗后读屏用户需要能感知到“现在的上下文变了”之前页面级的地标导航应该被暂时封锁焦点必须进入对话框。这不是 Landmarks Pattern 的直接职责但如果你在为弹窗写代码时能考虑到“用户从哪里来、焦点该如何回到哪里”你对整个无障碍体系的理解就更完整了。对于一个前端团队来说推广 Landmarks Pattern 的最佳方式不是单独搞一套规范文档而是把它融进代码规范和组件库设计里。比如在你的组件库 Button 组件旁边加一个“页面地标”使用文档在路由配置里约定每个页面必须有且仅有一个 main在 ESLint 规则里强制对多个 navigation 命名。这些事看起来很小组合在一起就是一套能被团队持续执行的无障碍基建。最后再分享一个我实际项目中的经验不要试图“一次把所有页面改完”。Landmarks 的改造非常适合按页面类型渐进推动先改全局共享的布局壳页头、导航、页脚再改高频落地页最后再逐页处理低频长尾页面。因为全局壳是每个页面都复用的改一次就能覆盖全站多数页面性价比极高。我当年就是先花半天把布局壳改完再顺手加了 CI 规则之后新页面再没有出现过“无地标”的回归整个改造的投入产出比可以说是非常划算了。