role属性仅在原生HTML无法表达组件真实角色时精准声明,如div实现的开关需role="switch"+aria-checked+tabindex="0",下拉菜单需role="listbox"+aria-expanded+aria-activedescendant,模态框需role="dialog"+aria-modal="true"+aria-labelledby;不得滥用或覆盖原生语义。

Role 属性不是语义“补丁包”,而是当原生 HTML 标签无法表达组件真实角色时,才必须介入的精准声明。给 div 加 role="button" 却不处理焦点和键盘事件,等于在界面上贴了一张“此物可点”的纸条,但门锁着。
哪些自定义组件必须用 role 补语义?
只有三类场景真正需要:用 div 或 span 实现的控件、旧项目无法重构的容器、动态内容区域缺乏结构性提示。其他情况优先用 <button>、<select>、<nav> 等原生标签。
- 自定义开关(非
<input type="checkbox">)→ 必须用role="switch"+aria-checked+tabindex="0" - 下拉菜单容器(非
<select>)→ 必须用role="listbox"+aria-expanded+aria-activedescendant - 模态框(非
<dialog>)→ 必须同时满足role="dialog"+aria-modal="true"+aria-labelledby指向标题元素 - 日历网格外层容器 → 一般用
role="region"+aria-labelledby,而非role="application"(后者会强制关闭读屏浏览模式)
加了 role 为什么屏幕阅读器还是“看不见”?
常见错误不是没写 role,而是只写了 role。辅助技术看到 role="slider" 就认定这是滑块,但若缺 aria-valuenow、aria-valuemin、aria-valuemax,它就只能报“滑块”,不读值、不提示范围、不支持方向键调节。
-
role="combobox"必须配aria-expanded和aria-controls(指向弹出列表 ID) -
role="tablist"下每个tab必须有role="tab"+tabindex="0"+aria-selected,且需 JS 同步更新状态 - 图标按钮(如仅含
<svg>)必须用aria-label或aria-labelledby提供名称,否则读屏器跳过或读作“图形” - 所有带
role的交互元素,都得手动加tabindex="0"并监听keydown处理 Enter/Space/方向键
role 和原生标签混用会出什么问题?
浏览器对原生标签有强语义共识,强行覆盖会破坏状态同步。比如给 <button> 加 role="link",读屏器会朗读为“链接”,但按回车不会触发默认跳转逻辑,反而让用户困惑“这到底是按钮还是链接?”
立即学习“前端免费学习笔记(深入)”;
-
<input type="checkbox">已自带role="checkbox"和自动管理的aria-checked,再手动设aria-checked="true"可能导致 JS 状态与 AT 状态不同步 -
<nav>等价于role="navigation",再显式写<nav role="navigation">是冗余,部分校验工具会警告 -
<main>页面中只能出现一次,且不能嵌套在<article>或<section>内,否则结构树错乱,读屏器可能跳过主体内容 -
role="presentation"或role="none"只用于纯装饰性容器(如仅做布局的div),绝不能用在含文本或功能的区域上
最易被忽略的一点:role 声明后,所有对应行为(焦点、键盘响应、状态更新、命名)必须由你全权负责——没有浏览器兜底,也没有读屏器容错。它不是声明“这是什么”,而是签了一份契约:“我保证它 behaves like what I claim”。



















