)、动态 JS 插入且无上下文的区域(如弹窗、通知条)。

绝对不能加 role 的是已有明确原生语义的元素:button、nav、main、h1、input 等。例如:

  • :冗余,可能干扰焦点管理或导致读屏重复播报
  • :覆盖后让屏幕阅读器误判为页头,破坏 landmark 结构
  • :语义与行为脱节,空格键无法切换,AT 会报“不可操作”

role="dialog" 必须配齐的三个硬性条件

只写 role="dialog" 几乎等于没写——屏幕阅读器只会读出“对话框”,其余行为全无保障。

  • 必须设 aria-modal="true"(WAI-ARIA 1.1 强制要求;IE 不支持,需降级 fallback)
  • 必须配 aria-labelledby,且值指向一个真实存在、可见、未被 aria-hidden="true" 或 display: none 隐藏的标题元素 ID
  • 必须手动实现焦点陷阱:打开后 focus() 到第一个可交互子元素;Tab 键循环限制在弹窗内;关闭后 focus() 回触发按钮

漏掉任意一项,视障用户可能完全感知不到弹窗,或操作后焦点丢失在页面顶部。

role 单独存在基本没意义

每个 role 都绑定一套最小语义契约,缺配套属性就等于无效声明:

HTML Text Extract
HTML Text Extract

从HTML页面(URL、文件或标准输入)中提取主体内容文本,去除导航、页脚、广告和模板文本。可直接传递给readability_check或其他文本工具。

下载
  • role="slider" 必须同步有 aria-valuenow、aria-valuemin、aria-valuemax,并响应方向键
  • role="tab" 必须嵌套在 role="tablist" 内,配合 aria-selected 和 tabindex,且对应 tabpanel 要靠 aria-hidden 控制显隐
  • role="alert" 必须搭配 aria-live="assertive",且内容应直接插入而非仅靠 JS 改 innerHTML(部分读屏器只读首次注入)

最常被跳过的是:JS 更新了视觉状态(比如切换了 tab 样式),却忘了同步设置 aria-selected 或 aria-expanded。辅助技术看到的是静止的 DOM,不是你的 CSS 类名。

验证 role 是否真正生效的关键动作

别只看代码写了没,要进真实环境检查:

  • Chrome DevTools → Accessibility 面板 → 点击元素,确认 Role 字段显示为你设置的值(不是 generic 或默认 fallback)
  • NVDA / VoiceOver 开启后,用 D 键(NVDA)或 Ctrl+Option+U(VoiceOver)打开 landmark 导航,确认你的 role="navigation" 出现在列表中
  • 禁用 CSS 后手动浏览结构:如果仅靠 role 而没有对应语义标签,纯 HTML 下仍是一堆 div,协作和维护成本反而升高

动态修改 role(比如从 button 改成 alert)不会触发语义重计算——大多数辅助技术只在元素插入 DOM 时读取一次 role。真需要切换角色(极少见),应销毁旧元素、重建新元素并重新聚焦。