aria属性与bem类名分工协作:bem管视觉状态与结构归属,aria管语义与交互状态;二者必须由js同步更新,否则屏幕阅读器呈现与用户所见错位。

ARIA 属性和 BEM 类名不是并列的“两套系统”,而是分工明确的协作关系:BEM 管结构归属与视觉状态,ARIA 管语义表达与交互状态;两者必须同步更新,否则屏幕阅读器看到的和用户看到的就对不上。
为什么不能靠 BEM 类名驱动 ARIA 状态
BEM 类名是 CSS 作用域工具,不被辅助技术读取。写 form-field--invalid 只会让样式变红,但 aria-invalid="true" 才会让屏幕阅读器说“邮箱格式错误”。常见错误:
-
form__input--error加了,但没同步设aria-invalid="true"和aria-describedby→ 键盘用户完全不知错在哪 - 用
button--disabled控制灰掉样式,却漏掉aria-disabled="true"和tabindex="-1"→ 焦点仍能进入,但点击无响应 - 折叠面板加了
accordion__panel--expanded,但aria-expanded还是"false"→ 屏幕阅读器提示“已收起”,实际内容已展开
哪些 ARIA 属性必须和 BEM 修饰符一一绑定
三类状态必须由 JS 同时操作 class 和 ARIA 属性,缺一不可:
-
aria-expanded↔block__element--expanded(如accordion__header--expanded) -
aria-checked↔checkbox__input--checked(注意:不是input[type="checkbox"]自带的 checked,而是自定义复选框的视觉状态) -
aria-invalid+aria-describedby↔form-field--invalid(aria-describedby必须指向form-field__message的真实id)
关键点:aria-describedby 指向的 ID 必须存在且唯一;若校验文案动态切换(空值 vs 格式错),JS 要改的是这个 ID 引用,而不是新增 form-field--empty 这类修饰符。
如何避免 class 和 ARIA 不同步的坑
最可靠的同步方式是封装状态更新逻辑,而不是分散在各处手写:
- 别在多个地方分别调
el.classList.add("field-email--invalid")和el.setAttribute("aria-invalid", "true") - 统一用函数驱动:
setFieldValidity(fieldEl, isValid, messageId),内部同时处理 class、aria-invalid、aria-describedby、aria-errormessage - 禁止用 CSS 伪类(如
:invalid)自动触发 ARIA —— 它不支持初始空值校验,iOS Safari 对:user-invalid支持极差 - 微前端场景下,
form-field的 block 名可能带子应用前缀(如sales-portal__field-email),对应 ARIA ID 也得动态生成,不能硬编码
BEM 元素命名直接影响 ARIA 可维护性
一个合法的 BEM 结构,能让 ARIA 维护变得可预测:
-
form-field__message是固定元素名 → 它的id可稳定用于aria-describedby,无需每次生成随机 ID -
field-email__input明确归属 → JS 获取该 input 并设置aria-invalid时,不会误操作到field-phone__input - 禁用
input--invalid这种脱离 block 的简写 → 否则搜索grep "__input--invalid"就找不到所有校验字段,ARIA 同步逻辑必然遗漏
真正难的不是写对第一个 aria-expanded,而是保证第 37 个折叠项、第 12 次表单提交、第 5 次语言切换后,它依然和 class 严丝合缝——这靠的是 BEM 的结构约束力,不是靠人盯。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











