role属性是语义覆盖开关,仅在原生标签无法表达真实意图时使用,且必须配齐对应行为和aria-*属性,否则比不加更危险。

role 属性不是“加了就无障碍”,而是语义覆盖开关——只在原生标签表达不了真实意图时才用,且必须配齐对应行为和 aria-* 属性,否则比不加更危险。
哪些元素必须加 role,哪些绝对不能加
必须加 role 的只有三类情况:
• 用 div 或 span 实现的自定义控件(如下拉菜单容器、滑块、开关)
• 旧项目中无法重构但需补结构语义的容器(如 <div class="main-content">)<br>• 动态 JS 插入且无上下文的区域(如弹窗、通知条)<p>绝对不能加 role 的是已有明确原生语义的元素:<br>• <code>button 再加 role="button":冗余,可能干扰焦点管理或导致读屏重复播报
• nav 加 role="banner":覆盖后让屏幕阅读器误判为页头,破坏 landmark 结构
• input type="checkbox" 加 role="switch":语义与行为脱节,空格键无法切换,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 都绑定一套最小语义契约,缺配套属性就等于无效声明:
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 是静态契约,不是运行时指令。辅助技术只在元素插入 DOM 时读取一次 role,后续 JS 修改(比如从 role="button" 改成 role="alert")不会触发语义重计算。











