必须用--而非-或_,因--是BEM中唯一合法修饰符分隔符,明确标识“块的临时状态”,保障语义锚点、工具链识别与团队协作一致性;-易被误判为独立组件,_无约定含义且不被自动化工具识别。

为什么必须用 -- 而不是 - 或 _
BEM 规范里,-- 是修饰符(Modifier)的唯一合法分隔符。它不是“写起来顺手”的风格选择,而是语义锚点:告诉团队、工具链和你自己——这个类名表达的是「块的临时状态」,不是新组件,也不是元素子项。
常见错误现象:btn-disabled 在 PurgeCSS 里被删掉,DevTools 里看不出它是按钮状态还是独立组件;btn_disabled 被自动化命名工具忽略,因为没匹配 btn--* 模式。
-
btn--disabled✅ 解析为「btn 块的 disabled 状态」 -
btn-disabled❌ 被识别为全新块名,破坏可维护性 -
btn_disabled❌ 下划线在 BEM 中无约定含义,无法被解析为 Modifier
哪些状态该用 -- 修饰符,哪些不该
修饰符只管「需要 JS 控制、有明确业务含义、可能影响交互逻辑」的状态。纯视觉反馈或伪类行为(如 :hover、:focus)不该塞进 -- 里,否则会模糊职责边界,也难做 E2E 测试。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 该用:
rating__star--selected(用户已点选)、form--submitting(API 正在发请求)、input--error(校验失败) - 不该用:
btn--hover(应直接用.btn:hover)、card--focus(应靠:focus-within或原生 focus 状态) - 禁用整个组件时,修饰符要加在 Block 上:
rating--readonly,而不是给每个rating__star单独加--disabled
JS 切换 -- 类时最常踩的坑
手动拼字符串、漏空格、重复添加、SSR hydration 不一致——这些都会让状态看起来“有时生效有时不”。classList.toggle() 是浏览器原生支持的原子操作,不依赖 DOM 结构,也不怕多次调用。
立即学习“前端免费学习笔记(深入)”;
- 推荐:
el.classList.toggle('btn--loading', isLoading) - 避免:
el.className += ' btn--loading'(重复加、无法移除、覆盖其他 class) - React/Vue 中注意:state 改了,但 class 没同步更新,往往是因为忘了把
isDisabled映射到 className 表达式里 - 多个状态共存时(如
btn--primary btn--disabled btn--loading),每个修饰符 CSS 规则必须独立声明,禁止写组合选择器.btn--disabled.btn--loading——维护成本高,漏一个类就失效
CSS 里写 -- 修饰符样式的硬约束
所有修饰符样式必须绑定到具体 Block 或 Element 上,不能泛写 .is-loading 或 [class*="is-"]。否则 PurgeCSS 会误删,DevTools 里也看不出样式归属。
- 正确:
.btn.btn--disabled { opacity: 0.6; }、.rating__star.rating__star--hovered { transform: scale(1.1); } - 错误:
.is-disabled { opacity: 0.6; }(脱离上下文,易被删) - 性能注意:
btn--loading里慎用多层box-shadow或filter: blur(),高频切换时容易掉帧 - 层叠顺序容易被忽略:多个
--类同时存在时,最终效果由 CSS 优先级决定,不是 class 列表顺序。别指望btn--primary btn--disabled和btn--disabled btn--primary渲染不同——它们完全等价
btn--disabled,但客户端 JS 初始化后没同步设置 isDisabled,就会触发 hydration mismatch,导致 class 被 React/Vue 强制重置。

















