直接写 .btn--disabled 更可靠,因BEM修饰符用--前缀将状态锁死在组件内,避免外部样式覆盖、DOM层级依赖及语义混淆,且需绑定具体UI表现、独立存在、统一命名规范。
.btn--disabled 比 .btn.disabled 更可靠因为 BEM 的修饰符(--)把状态锁死在组件内部,不会被外部样式或 JS 动态类名意外覆盖。比如你用 JS 切换 disabled 类,但父容器有个 .form--compact .btn { opacity: 0.6; },就可能和禁用态视觉冲突;而 .btn--disabled 是独立命名,CSS 优先级一致,也不依赖 DOM 结构层级。
常见错误是把修饰符当布尔开关乱加:.btn--primary--disabled 这种连写会让语义模糊、难以维护。BEM 要求每个修饰符只表达一个状态维度。
--primary、--small、--disabled
.btn--primary.btn--disabled 是合法的,但不要合并成 .btn--primary-disabled
el.classList.toggle('btn--disabled', isDisabled),而不是拼字符串很多人写 .card--error,结果发现这个“error”既表示表单校验失败,又表示网络请求失败,还用来标红整个卡片——最后谁也不知道该不该复用它。修饰符必须绑定具体 UI 表现,而不是业务逻辑。
比如“加载中”状态,.btn--loading 就比 .btn--busy 清晰,因为前者明确对应转圈图标 + 禁用交互;后者可能让人误以为只是暂时不可点,没视觉反馈。
立即学习“前端免费学习笔记(深入)”;
--hover、--focused、--expanded,而不是业务词:--failed、--pending
.input--error.input--focused),说明它其实该拆成两个独立修饰符React 里硬写 className="btn btn--primary btn--disabled" 确实难看,但盲目上 clsx 或 classnames 也不解决问题——容易把状态逻辑塞进模板,导致样式和组件逻辑耦合。
更稳的做法是把修饰符生成逻辑收口到组件内部,用对象映射控制输出:
const modifiers = { 'btn--primary': variant === 'primary', 'btn--disabled': disabled, 'btn--loading': loading,};
Vue 的 :class 同理,别用三元拼接,用对象语法保持可读性。
{`btn--${size}`}
.btn--SMALL 和 .btn--small 是两个类不是所有状态都该塞进修饰符。比如深色模式切换,.theme-dark .btn 就比 .btn--dark-theme 更合理——因为它是全局上下文,影响所有组件,不是按钮自身的状态。
还有像“鼠标移入子元素才显示删除按钮”这种纯交互态,用伪类 .item:hover .item-delete 更轻量,没必要加 --hover 修饰符再靠 JS 控制。
:hover、:focus-within),别强行用修饰符替代transition 属性,且确保初始态有明确 height/opacity 值BEM 修饰符真正管用的地方,是那些“组件自己说了算”的稳定视觉变体。一动不动的 class 名,比随时可能被 JS 改来改去的变量名,更容易被团队对齐、被工具检查、被缓存复用。