原生<button>自带键盘交互、焦点管理和语义,而role="button"仅声明角色,不提供功能;必须手动加tabindex="0"、监听Enter/Space、同步状态,否则键盘用户无法操作。

role="button" 为什么不能替代原生 <button>
原生 <button> 自带键盘交互(空格/回车触发)、焦点管理、默认可访问性语义;而仅加 role="button" 的 <div> 或 <span> 不会自动获得这些能力,屏幕阅读器虽能读作“button”,但键盘用户无法操作。
- 必须手动监听
keydown事件,判断event.key === 'Enter'或' '并触发逻辑 - 需显式设置
tabindex="0"才能获得焦点,且要处理focus/blur状态样式 - 若内部含文字,还要确保不被误读为“button 包含段落”——建议用
aria-label覆盖或包裹纯文本节点
用 role="navigation" 时漏掉 aria-label 的后果
多个 role="navigation" 区域共存时(如页眉导航、侧边栏、页脚链接组),屏幕阅读器仅靠 role 无法区分用途,会连续读作“navigation, navigation, navigation”。
- 必须为每个导航区提供唯一标识:
aria-label="主导航"、aria-label="页脚资源链接" -
aria-labelledby可复用页面已有标题元素(如<h2 id="nav-main">网站导航</h2>),但需确保 ID 存在且未被重复引用 - 不要依赖视觉位置推断——无障碍设备不“看布局”,只按 DOM 顺序和属性解析
aria-hidden="true" 和 display: none 的语义差异
display: none 会让元素彻底从渲染树和无障碍树中移除;而 aria-hidden="true" 仅屏蔽无障碍访问,元素仍可聚焦、仍响应鼠标事件、仍参与 CSS 布局。
- 模态框背景遮罩层常用
aria-hidden="true"配合inert(现代浏览器支持)或手动tabindex="-1"+ 移除焦点流,避免键盘陷入不可操作区域 - 图标字体或装饰性 SVG 若设
aria-hidden="true",需确认其父容器未意外暴露冗余文本(比如<span aria-hidden="true"></span>菜单</span>中的“菜单”才是有效文本) - 慎用于动态内容:React/Vue 中组件销毁前若先设
aria-hidden="true",可能造成读屏器短暂卡顿或跳读
自定义组件该用 role="combobox" 还是 role="listbox"
二者语义不同:role="combobox" 是“可输入+可选列表”的复合控件(如带搜索的下拉),必须包含一个 input 和一个关联的 role="listbox";role="listbox" 是纯选项列表,无输入能力。
立即学习“前端免费学习笔记(深入)”;
- 若组件只有下拉菜单、无搜索框,直接用
role="listbox"+role="option",并确保aria-activedescendant同步当前高亮项 - 若用了
role="combobox",必须用aria-controls指向对应listbox的 ID,且input需设aria-autocomplete="list" - 所有
option必须有唯一id,否则aria-activedescendant失效,键盘导航(上下键)将无法工作
最常被忽略的是焦点同步逻辑:当用户用键盘选择选项时,焦点仍在 input 上,但语义焦点(aria-activedescendant)必须实时更新——这一步不做,读屏器就不知当前选中哪个项。



















