progress元素必须用aria-labelledby关联可见文本,否则屏幕阅读器无法识别;需配合aria-valuetext动态播报进度值,禁用title替代无障碍标签,并在旧浏览器中降级为role="progressbar"模拟。

progress 元素必须用 aria-labelledby 关联可见文本
单纯写 <progress value="60" max="100"></progress> 对屏幕阅读器完全不可读——它既不播报当前值,也不说明这是什么进度。必须用 aria-labelledby 指向一个明确描述用途的文本节点,比如“文件上传进度”。
常见错误是只加 aria-label:虽然能读出来,但会覆盖原生语义,且无法动态更新数值描述(如“已完成 60%”)。而 aria-labelledby 可以配合实时更新的辅助文本一起用。
- 目标文本需是可见元素(
<span></span>、<p></p>等),不能是display: none或visibility: hidden - ID 必须唯一,且大小写敏感;引用时 ID 值要和
id属性完全一致 - 不要用
aria-describedby替代——它用于补充说明,不是主标签,多数读屏默认不播报
需要动态更新百分比文字时,用 aria-valuetext 配合 aria-labelledby
当 value 改变(比如从 30 变到 75),仅靠关联的静态标题(如“上传中”)不够。得让屏幕阅读器同时读出“已完成 75%”,这时要在 <progress></progress> 上加 aria-valuetext。
注意:aria-valuetext 不会自动计算,必须由 JS 手动设置。而且它只在值变化时触发播报,前提是 aria-labelledby 已存在并指向有效文本。
- 更新逻辑示例:
progressEl.setAttribute('aria-valuetext', `已完成 ${Math.round(value)}%`) - 避免重复冗余,比如标题已是“备份进度”,
aria-valuetext就别再写“备份进度:已完成 75%”,直接“已完成 75%”更自然 - 若进度条无明确单位(如“加载中…”而非具体百分比),
aria-valuetext可设为“正在处理”并随状态切换
避免用 title 属性替代无障碍标签
title 属性对键盘用户和多数屏幕阅读器无效——它只在鼠标悬停时显示 tooltip,且移动端基本不触发。把它当无障碍替代方案等于没做。
更麻烦的是,title 还可能干扰焦点管理:某些旧版 NVDA 会把 title 当作额外标签播报,导致重复或错乱。
- 绝对不要这样写:
<progress title="上传进度:60%"></progress> - 如果真需要 tooltip 辅助 sighted 用户,另加一个带
role="tooltip"的元素,并用aria-describedby关联(但仅作补充,不替代主标签) - 所有无障碍核心信息必须通过语义 HTML + ARIA 显式声明,不能依赖 CSS 或交互态提示
progress 在旧浏览器或低版本读屏中的兼容性风险
IE 和部分 Android WebView 不识别 <progress></progress> 的原生语义,即使加了 aria-labelledby,也可能被当作普通内联元素跳过。这时候得靠降级策略兜底。
最稳妥的做法是:用 <div role="progressbar"> 完全模拟,并手动维护 <code>aria-valuenow、aria-valuemin、aria-valuemax ——哪怕现代浏览器用原生 <progress></progress>,也建议在初始化时检查是否支持,不支持则替换 DOM。
- 检测方式:
'max' in document.createElement('progress') - 必须同步设置
role="progressbar"和全部三个 ARIA 数值属性,缺一不可,否则读屏无法理解进度含义 - 视觉样式上,
<div> 模拟需确保 focusable(加 <code>tabindex="0"),否则键盘用户无法聚焦查看当前值 实际项目里最容易被忽略的,是aria-valuetext的手动同步时机——它常被写在异步回调里,但若 JS 报错或 Promise 被取消,这个值就卡住不动,导致读屏播报严重滞后甚至错误。务必在所有可能修改value的路径上都覆盖更新逻辑。











