四、ARIA标签与角色归纳总结

ARIA 这玩意儿,说白了就是给 HTML 元素“补户口”的。很多原生元素,比如按钮、输入框,浏览器自己就能告诉辅助技术“我是个啥,我能干啥”。但一旦你用了 <div> 做按钮,用 <span> 做滑块,浏览器就懵了——它不知道这些东西到底要干嘛。ARIA 就是来填这个坑的。

我最早接触 ARIA 是在一个国际化后台系统里。产品经理说“盲人用户也要能用”,我当时心里想:加几个 role 和 aria-label 不就行了?后来踩过坑才明白,ARIA 用错了比不用更可怕。

4.1 ARIA 角色分类

WAI-ARIA 1.2 定义了三大类角色,你想想看,就像公务员系统里的编制:

  • Landmark(地标角色)——给页面分区的,比如 banner、navigation、main、contentinfo。屏幕阅读器用户可以跳着读,不用从头扫到尾。
  • Widget(小部件角色)——用来做交互组件,比如 button、slider、tab、dialog。它告诉辅助技术“这个东西能点、能拖、能切换”。
  • Document Structure(文档结构角色)——介于两者之间,比如 article、section、heading。它们帮用户理解内容层次。

我个人习惯把角色理解成“标签的职责”。比如 role="navigation" 就是告诉屏幕阅读器“这里是导航区,你可以跳过或直接定位”。

常用 ARIA 角色速查表
角色分类用途
bannerLandmark网站头部(通常包含logo和站点名)
navigationLandmark主要导航区域
mainLandmark页面主要内容区
buttonWidget可点击的按钮(非原生按钮)
tabWidget标签页组件里的单个标签
dialogWidget模态对话框
articleDocument Structure独立的内容单元(如博客文章)

4.2 ARIA 属性与状态

角色告诉“你是什么”,属性和状态告诉“你现在的细节”。比如 aria-label 给元素一个可读的名称,aria-expanded 告诉用户某个区域是否展开。

核心原则:能用原生 HTML 语义标签,就尽量不用 ARIA。<button> 天然就是 button 角色,你不需要再写 role="button"。ARIA 是兜底方案,不是首选。

记得有一次我接手一个国际化项目,里面有个自定义下拉框,用 <div> 做的。视觉上完全没问题,但屏幕阅读器读出来就是“div,collapse,group”。用户根本不知道这是个可选择的下拉菜单。后来加了 role="combobox"、aria-expanded="false" 和 aria-controls,才算是正经能用了。

常用的属性/状态我列了个清单:

  • 标签类:aria-label、aria-labelledby、aria-describedby
  • 关系类:aria-controls、aria-owns、aria-activedescendant
  • 状态类:aria-expanded、aria-pressed、aria-selected、aria-disabled
  • 实时区域:aria-live、aria-relevant、aria-atomic

避坑技巧:我曾经在动态加载列表时忘了给容器加 aria-live="polite",结果屏幕阅读器完全没反应,用户以为页面死掉了。后来在 CI 里加了 axe-core 检查这种遗漏。

4.3 实战:在 CI 中自动校验 ARIA

你想想看,光靠人工 review 哪能记得住所有规则。我在 CI 流程里集成两个工具:@axe-core/puppeteer(浏览器级检查)和 eslint-plugin-jsx-a11y(代码级检查)。

一个典型的 CI 配置示例(.github/workflows/a11y.yml):

name: Accessibility Check
on: [pull_request]
jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx axe-puppeteer http://localhost:3000
        env:
          CI: true

不过要注意,axe 默认规则集可能漏掉一些国际化相关的 ARIA 用法。比如 aria-label 里如果写了英文,但页面语言是中文,这其实是一种隐性的无障碍问题。我额外加了一条规则检查 aria-label 的语言是否匹配 lang 属性。

4.4 常见错误与最佳实践

  • 滥用 role="presentation":把语义元素变成纯表现层,会让导航信息丢失。慎用。
  • 忘记更新 aria-expanded:手风琴组件展开后状态不变,辅助技术读到的还是闭合。建议用 JS 强制同步。
  • aria-label 替代了可见文本:如果按钮上已经有文字了,别再加 aria-label,会覆盖掉屏幕阅读器的自然读取顺序。
  • tabindex 与角色冲突:比如 role="button" 但没加 tabindex="0",键盘完全无法聚焦。

警告:aria-hidden="true" 会从无障碍树中完全移除元素。我曾见过有人把整个侧边栏都 hidden 了,结果用户直接忽略了一大块导航。只在真正不需要展示时用。

4.5 知识框架总览

下面这张图把 ARIA 的核心要素串起来了。从角色、属性、状态出发,最终映射到浏览器生成的无障碍树。你可以在 CI 环节对照这张图去检查自己的组件。

ARIA 标签与角色知识框架 ARIA 规范 角色 (Roles) 属性 (Properties) 状态 (States) Landmark Widget Document Struct. aria-label aria-labelledby aria-controls aria-live aria-expanded aria-pressed aria-selected 浏览器无障碍树 (Accessibility Tree)

这张图告诉你一个关键点:角色、属性、状态三者共同决定了元素在无障碍树里的最终表现。任何一个环节缺失或错误,都可能让辅助技术“读不懂”。

4.6 小结

ARIA 不是魔法,它只是 HTML 语义化的补充。用好它的核心原则就三条:能原生就别用 ARIA、用了就必须保持同步更新、在 CI 里自动检查比人工 review 靠谱得多。

我自己的项目里,我会把 ARIA 检查的阈值设成 error 阻断 CI 通过。一开始大家嫌烦,但习惯了之后,改掉的 bug 至少有三分之一都是 ARIA 相关。你想想看,这点代价换来的是对所有用户的公平,值不值?