bem 通过 block__element--modifier 结构明确样式归属、层级与状态,避免类名冲突和结构失效。错误命名如 .button 或 .card .title 会导致覆盖或失效,正确写法需严格遵循语法契约,js 拼接应使用工具防错,核心是每次写 class 前思考“属于谁”和“是否会在其他上下文生效”。

直接用 .button 或 .header 必然导致样式冲突,BEM 不是让名字变长,而是用 block__element--modifier 把归属、层级、状态全锁进类名里,让冲突从“随机覆盖”变成“结构错误,一眼可查”。
为什么 .button 一定会炸锅
浏览器不认“谁写的”,只按层叠顺序和选择器权重匹配类名。只要两个文件都定义了 .button,后加载的就会生效;哪怕加了 !important,也只是把问题推给下一个人。更糟的是,A 模块改 .button:hover 背景色,B 模块的按钮也会跟着变——因为没上下文绑定。
-
.user-card__avatar和.profile-card__avatar天然不同名,共用同一份 CSS 文件也不会互相干扰 - 错误写法:
.card .title—— DOM 加个 wrapper 就失效;正确是.card__title,不依赖父级结构 - 第三方库(如 Ant Design)的
.ant-btn不能硬套 BEM,应包一层 wrapper block,比如.form-field--date
__ 和 -- 不是风格偏好,是语法契约
所有基于 BEM 的工具链(stylelint-selector-bem-pattern、VS Code 插件、Webpack CSS 提取逻辑)都靠这个结构做正则识别。写错就等于断掉自动化校验能力。
-
.search-form__input--disabled✅(search-form是 block,input是其元素,disabled是状态) -
.search-form__input_error❌(单下划线非标准,应为--error) -
.button--primary❌(缺少 block 前缀,button不是合法 block 名,应为checkout-button--primary) -
.card__header--large--dark❌(修饰符不能嵌套,应拆成.card__header--large .card__header--dark)
JS 里拼 className 容易踩哪些坑
手拼字符串看着快,实际极易漏空格、错连字符、大小写混用,而且构建环境对大小写敏感度不同,本地跑得通,CI 上直接挂。
- ❌ 危险写法:
className={`button button--${variant} ${hasIcon ? 'button__icon' : ''}`}(缺空格、无防错、button__icon脱离 block) - ✅ 推荐用
clsx或封装常量:const BLOCK = 'search-form'; const cn = (e, m) => `${BLOCK}${e ? '__' + e : ''}${m ? '--' + m : ''}` - Vue/React 中避免直接写
class="search-form__input"后再 JS 控制显示隐藏——要用search-form__input--hidden这类 modifier,保持结构一致 - 禁止在组件内定义局部变量存类名(如
const iconClass = 'button__icon'),这会让 DOM 结构语义断裂,审查时无法一眼识别归属
BEM 真正难的不是记规则,而是每次写 class 前多问一句
“这个样式属于谁?它会不会在别的上下文里意外生效?”——这句话比任何命名规范都管用。Block 名必须对应真实容器节点(比如 search 对应 <div class="search">),Element 名要直接描述角色(<code>search__input,别用 search__wrapper),Modifier 只表达状态(button--disabled,别写 button--full-width)。所有子元素都应有完整 BEM 类名,不省略 Block;search__input 不能简写成 __input。











