BEM类名不能代替ARIA属性,因其仅用于样式和开发者维护,而aria-expanded等属性才是可访问性API识别状态的唯一依据;必须由JS手动同步或通过语义HTML绑定。

为什么BEM类名不能代替ARIA属性
BEM类名是给开发者和CSS用的,aria-label、aria-expanded这些才是给屏幕阅读器和浏览器可访问性API用的。哪怕你写了nav__toggle--expanded,只要没同步加aria-expanded="true",NVDA或VoiceOver就完全不知道这个按钮当前是展开还是收起状态。
常见错误现象:
– 折叠面板点击后视觉变了,但读屏器仍报“折叠”;
– button--primary写在<div>上,键盘Tab进不去,空格/回车也不触发;
– 图标用::before + icon--search,但没配aria-label,搜索功能对视障用户彻底不可见。
- BEM的
--expanded修饰符只负责样式切换,它不自动映射到可访问性状态 - 必须由JS手动同步设置
el.setAttribute('aria-expanded', 'true') - 如果用纯CSS方案(如
:checked控制折叠),也要用aria-expanded绑定到对应控件,不能依赖伪类
哪些ARIA属性必须和BEM修饰符联动
不是所有ARIA都得配修饰符,但以下三类必须严格同步,否则会出现“视觉可见、AT不可知”的断裂:
-
aria-expanded:对应accordion__header--expanded或menu__toggle--open -
aria-selected:对应tab__item--active或menu__item--selected -
aria-disabled:对应button--disabled或form-field--disabled,不能只靠disabled属性——因为button--disabled可能挂载在<span>上,原生disabled无效
注意:aria-hidden="true"一般不配修饰符,而是直接写在结构里;role应优先通过语义标签实现(如用<nav>代替<div class="nav">),BEM只是辅助确认边界。
立即学习“前端免费学习笔记(深入)”;
怎么避免BEM命名污染ARIA语义
最常踩的坑是把BEM当语义容器用,结果反而掩盖了真实意图。比如:
- 写
<div class="alert__content">却忘了alert本身该是role="alert",而div默认无角色 - 用
product-card__title套在<div>里,却不加aria-level="2"或改用<h3>,标题层级对读屏器丢失 - 把
breadcrumb__separator硬塞进HTML(如<span class="breadcrumb__separator">/</span>),而不是用::after生成,导致分隔符被读出来
正确做法:
– Block名(如alert)优先绑定WAI-ARIA官方角色名;
– 所有Block根节点检查是否需要role,再决定用<div>还是<section>等语义标签;
– Element(如alert__icon)若为纯装饰,必须加aria-hidden="true";若承载信息(如状态图标),要配aria-label或aria-live。
表单验证中BEM与ARIA协作的关键点
表单是最容易出无障碍问题的场景,field-email--invalid这类修饰符必须带动整个可访问性链路:
- 修饰符必须挂在字段容器(如
<div class="field-email field-email--invalid">),不能挂在input上 - 同时设置
aria-invalid="true"和aria-describedby="field-email-error",让错误文案ID可关联 - 错误文案元素(
field-email__error)需加role="alert"或aria-live="polite",确保变化被读出 - 初始空字段别用
field-email--valid——它既无语义必要,又干扰读屏器判断;“未校验”状态靠默认样式即可
复杂点在于:移动端 Safari 对:user-invalid支持差,纯CSS方案不可靠;而BEM+ARIA组合是唯一能跨浏览器稳定传递状态的方式。别省那几行JS同步代码,它决定了键盘用户能不能完成表单。


















