aria-invalid必须与field-*--invalid类同步操作,且三者(class、aria-invalid、aria-describedby)需原子化更新,否则屏幕阅读器无法准确播报错误;aria属性必须挂载在字段级block容器上,不可仅依赖css类或放在input元素上。

aria-invalid 必须和 field-*--invalid 类同步操作
单独写 aria-invalid="true" 不起作用,屏幕阅读器不会读出错误,因为没关联到具体错误文案。BEM 状态类(比如 field-email--invalid)不只是视觉开关,它得和 ARIA 属性联动,否则无障碍链就断了。
常见错误是 JS 只改 class,忘了同步 aria-invalid 和 aria-describedby:
- 漏设
aria-invalid="true"→ 屏幕阅读器认为字段“没问题” -
aria-describedby指向的 ID 不存在或拼错 → 错误文案不被朗读 - 多个字段共用同一个 error ID → 朗读内容错乱
正确做法是把这三个动作绑成原子操作:
function updateFieldValidity(fieldEl, isValid, errorId) {
fieldEl.classList.toggle('field-email--invalid', !isValid);
fieldEl.setAttribute('aria-invalid', !isValid);
if (errorId) {
fieldEl.setAttribute('aria-describedby', errorId);
} else {
fieldEl.removeAttribute('aria-describedby');
}
}
不要用 BEM 修饰符替代 ARIA 的语义职责
BEM 类名(如 field-password--weak)负责样式和 JS 控制粒度,但不能代替 aria-live、aria-atomic 或 role="alert" 这类声明式语义。它们分工不同:BEM 是“怎么变”,ARIA 是“告诉辅助技术这是什么”。
典型踩坑点:
- 只加
field-password--weak,却不给错误容器加role="alert"→ 弱密码提示静默,不主动播报 - 用
field-email--empty表示空值,但没设aria-required="true"→ 屏幕阅读器无法预知该字段必填 - 把
aria-current="page"写在tabs__item--active上 → 语义冲突,tab 应该用aria-selected="true"
状态类和 ARIA 属性要各司其职:field-email--invalid-format 控制图标、边框、错误文案显隐;aria-invalid 和 aria-describedby 确保这些变化被可访问性树捕获。
field-* 容器必须同时承载 class 和 ARIA 属性
ARIA 属性不能挂在 input 上再指望它“冒泡”到父级。比如 <input aria-invalid="true"> 不会让包裹它的 <div class="field-email"> 被识别为错误字段——可访问性树里它们是两个独立节点。<p>所以 <code>aria-invalid、aria-describedby、aria-labelledby 全部必须设在字段级 block 容器上,也就是那个带 field-email 的 <div>。这个容器才是语义完整的“一个字段”。<p>结构必须长这样:</p>
<pre class="brush:php;toolbar:false;"><div class="field-email" aria-invalid="false" aria-describedby="field-email-error">
<label class="field-email__label" for="field-email__input">邮箱</label>
<input id="field-email__input" class="field-email__input"><div id="field-email-error" class="field-email__error">请输入正确邮箱</div>
</div></pre>
<p>如果把 <code>aria-invalid 放在 input 上,CSS 选择器 .field-email--invalid .field-email__input 依然能生效,但屏幕阅读器根本不知道这个输入框属于哪个字段上下文。
多错误类型下,ARIA 描述 ID 必须动态更新
验证不是非黑即白,空、格式错、域名不在白名单,是三种不同错误。BEM 用 field-email--empty、field-email--invalid-format、field-email--invalid-domain 区分,但 ARIA 不支持“多状态描述”,aria-describedby 只能指向一个 ID。
这意味着你得动态切换 error 元素的 ID,或者复用同一个 ID 但更新其文本内容。前者更可靠:
- 每个错误类型对应唯一 ID:
field-email-error-empty、field-email-error-format - JS 校验后,根据错误类型设置
aria-describedby指向对应 ID - 所有 error 元素用
display: none隐藏,仅当前错误的元素display: block
别试图用 field-email--empty--format 这种叠加修饰符——CSS 权重失控,ARIA 也无法表达“二者同时成立”。真实校验结果只有一个主导错误,UI 和 ARIA 都该反映这个主错误。











