按钮应优先使用原生<button>,禁用冗余role="button";仅当必须用非原生容器时,才补全role="button"、tabindex="0"及键盘事件,并配aria-label或可见文本。

直接结论:按钮优先用原生 <button>,别加多余 role;只有在必须用非原生容器时,才补 role="button" + tabindex="0" + 键盘事件 + aria-label 或可见文本。
为什么 button 元素本身就不该加 role="button"
浏览器已为 <button> 自动映射 role="button"、焦点管理、Enter/Space 触发逻辑。手动加 role="button" 会覆盖原生语义,导致 NVDA 或 VoiceOver 把它误判成“自定义按钮”,可能跳过 disabled 状态或键盘行为。
常见错误现象:
- 按钮视觉正常但读屏器朗读为“段落”或“空白”——其实是用了
<div onclick>却没补全 ARIA - 点击后无反馈、键盘无法聚焦——漏了
tabindex="0"或没监听keydown - 读屏器说“按钮”,但用户不知道点它干啥——缺
aria-label或没配可见文本
图标按钮必须配 aria-label,不是可选项
<button><svg></svg></button> 这类结构,99% 的读屏器只报“按钮”,不报功能。用户无法判断是“删除”“设置”还是“关闭”。
立即学习“前端免费学习笔记(深入)”;
正确做法:
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
- 显式写
aria-label="删除这条消息",语义明确、无歧义 - 若按钮内有可见文字(如
<button><svg></svg> 提交</button>),通常无需aria-label——屏幕阅读器会自动合并播报 - 避免
aria-label和可见文本冲突,例如按钮显示“保存”,却写aria-label="提交表单",会造成认知混乱
自定义按钮容器(如 div)要补全三件套
当组件库封装或 CSS 限制迫使你用 <div class="btn"> 代替 <button>,必须手动补足语义和交互能力:
-
role="button":告诉辅助技术这是按钮角色 -
tabindex="0":让它能被 Tab 键聚焦(tabindex="-1"不行,无法进入 Tab 顺序) - 监听
keydown事件,捕获Enter和Space键并触发相同逻辑(仅靠onclick不够)
示例片段:
<div class="custom-btn" role="button" tabindex="0" aria-label="导出为 PDF" onclick="exportPDF()" onkeydown="if (event.key === 'Enter' || event.key === ' ') exportPDF()" >? 导出</div>
aria-live 不生效?检查 DOM 插入方式和位置
按钮点击后动态插入提示(如“已复制”),若读屏器没朗读,大概率是 aria-live 区域没被真实更新。
关键点:
- 新内容必须 直接 appendChild 或 textContent= 插入 到
aria-live容器内,不能用innerHTML = "..."替换整个容器(会销毁旧引用) - React/Vue 中需确保虚拟 DOM 更新真实反映到 DOM 节点上;用
ref或useEffect强制同步更稳妥 - 避免在
aria-live区域里嵌套可交互元素(如按钮),读屏器可能跳过或误读其状态
易忽略的细节:按钮本身不负责播报结果,而是触发后由独立 <div aria-live="polite"></div> 承担播报职责——这个区域必须常驻 DOM,且不被框架意外卸载。


















