BEM 的核心是用 block__element--modifier 结构将归属、角色、状态硬编码进类名,实现语义明确、作用域隔离与协作可追溯。

直接说结论:BEM 不是让 CSS 更“高级”的装饰,而是用 block__element--modifier 这种固定结构把“谁的、什么、当前什么状态”三个信息硬编码进类名里——新人打开一个 card__title--expanded,不用翻 HTML 或 JS 就能判断它只属于 card 块、是标题元素、处于展开态。
为什么 .btn 和 .button 在协作中必然扯皮
泛义类名没有上下文。多人并行时,.btn 可能被登录页加 margin-right: 8px,弹窗组件又给它加 display: block,没人知道哪个该保留。一旦某人写 el.classList.add('btn'),样式就从“某个按钮”变成“所有叫 btn 的东西”,覆盖风险肉眼不可见。
而 auth-form__submit 和 settings-form__submit 天然隔离,哪怕规则完全一样,也不会互相干扰。工具链(如 stylelint-selector-bem-pattern)也能靠双下划线识别这是元素,不是随便起的单词。
- 禁止用单下划线:
user_profile→ 工具无法识别为user块下的profile元素 - 禁止在修饰符里塞具体值:
button--width-200px→ 换成 rem 或响应式断点就得全局搜替换,且语义丢失 - 块名不能带样式描述:
red-card→ 后续换主题时得重命名整个块,而不是只改--theme-dark
如何让新人 3 分钟看懂一个 BEM 类名
靠文件组织 + 编辑器联动,不是靠讲规则。每个块对应一个独立文件,比如 button.css 开头加注释:
立即学习“前端免费学习笔记(深入)”;
/*<br>* @block button<br>* @description 主操作按钮,支持尺寸/主题/加载态<br>* @modifiers: --small, --large, --secondary, --loading<br>*/
VS Code 装 Auto Rename Tag 插件后,改 class="button__label" 会同步更新 CSS 文件里的 .button__label;再配 stylelint-config-bem,保存时立刻报错:Unexpected BEM element name "button__label-inner" ——说明你嵌套太深,该拆新块了。
- 每个
__element必须有对应 CSS 规则,不能靠继承或全局重置 - 修饰符永远叠加在块或元素上(
button--disabled或button__icon--hidden),不单独存在 - 禁止伪 BEM:
class="button button--loading"却只写了.button--loading,删掉修饰符整个按钮就崩
React/Vue 里怎么写不反人类
JSX 或模板里硬拼 className="card__header card__header--sticky" 确实难受,但切 CSS-in-JS 会丢掉 BEM 的可追溯性。真正解法是用工具函数封装,而不是手写字符串。
React 推荐 clsx:
className={clsx('card__header', { 'card__header--sticky': isSticky })}Vue 3 直接绑定对象:
:class="{ 'modal__overlay--visible': visible }"- 禁止在 JS 里硬编码:
el.classList.add('card__body')→ 应抽成常量或生成函数,否则重构漏一处就埋雷 - 组件库交付给其他团队时,BEM 的价值不在防污染,而在跨项目沟通——看到
search-form__input就知道它属于搜索表单,不是通用输入框 - 老代码允许豁免,但新增/修改部分必须遵守;CI 阶段跑
npx stylelint "**/*.{css,scss}"拦截非 BEM 类名
BEM 最容易被忽略的性能与协作陷阱
浏览器匹配快,不是因为名字短,而是因为没空格。.card__title--hovered 是单类名哈希查找;.card .card__title:hover 就得先找所有 :hover 元素再往上遍历父级,DOM 越深越慢。
但更常踩的坑是:BEM 只管命名,不管层叠顺序。如果 .card__title--large 和 .card__title--small 冲突,不是命名错了,而是 CSS 引入顺序或构建时打包顺序没管住——这需要 Webpack 配 mini-css-extract-plugin 控制输出顺序,或者用 PostCSS 的 postcss-sorting 统一声明顺序。
真正的复杂点从来不在语法本身,而在团队是否守住三条底线:不写伪 BEM、不跨块复用元素名、修饰符只描述状态不描述值。


















