BEM是Block-Element-Modifier命名规范,由Yandex提出,通过.block__element--modifier结构明确标识组件、其子元素及状态变体,解决CSS作用域冲突与样式污染问题。

BEM 不是让类名变长的仪式,而是用命名本身把“谁、在哪、什么状态”一次性说清。只要写对三处连接符(-、__、--),不依赖 HTML 结构、不靠注释、不翻 JS 就能看懂样式归属。
怎么起一个合法又语义清晰的 Block 名
Block 是独立功能单元,不是容器或布局词。它必须能脱离上下文单独存在、被复用、被测试。-
card、search-form、user-avatar合法:有明确职责,不依赖父级 -
section、container、wrapper非法:纯布局语义,无法描述功能,后续加__或--会失去上下文 - 多单词用
-连接,不用驼峰或下划线:date-picker✅,datepicker❌(歧义),date_picker❌(违反约定) - 禁止用业务缩写:
usr-avt比user-avatar更难维护,三个月后没人记得avt是啥
Element 必须依附 Block,且不能嵌套 Element
__ 表示“属于”,不是“在 DOM 里嵌套多深”。哪怕 card__title 在 HTML 里隔着三层 div,只要它是 card 的标题,就该叫这个。
- 正确:
card<strong>title</strong>、cardfooter、card__image - 错误:
card<strong>header</strong>title(元素不能嵌套元素)、card-title(丢失 Block 上下文,变成泛化选择器) - 元素名要语义化,不是结构描述:
card<strong>close-button</strong>✅,cardel-2❌(重构时完全无法推断用途) - 如果某个“子组件”需要独立样式逻辑(比如
card<strong>stats</strong>里还要分statsvalue),那它大概率该拆成新 Block:stat-badge
Modifier 只描述状态或变体,不表达尺寸/位置/颜色本身
-- 是开关,不是属性值拼接器。它回答的是“这个东西现在处于哪种可预期的状态”,而不是“我要给它加点红”。
- 合理:
button--disabled、modal--expanded、form-field--error - 常见错误:
button--red(颜色是表现,不是状态;应为button--primary或button--danger) - 更隐蔽的坑:
user-card--small(尺寸是响应式行为,不是固有状态;应配合媒体查询用user-card--compact或直接上@media控制) - 修饰符永远作用于 Block 或 Element 类名本身,不用于组合:
card<strong>title--large</strong>可以,但card--featuredtitle--large是非法结构,编译器不会报错,但人脑会卡住
Sass 嵌套时最容易踩的坑:& 不等于 BEM 层级
Sass 的& 是语法糖,不是 BEM 保障。它生成的选择器是否合法,得人工核对。
- 安全写法:
.card { &__header { /* → .card__header */ } &--featured { /* → .card--featured */ } } - 危险写法:
.card { &.is-open { /* → .card.is-open ✅,但不是 BEM 修饰符 */ } &__header.is-open { /* → .card__header.is-open ✅,但 is-open 不该挂元素上 */ } &.is-open &__header { /* → .card.is-open .card__header ❌,破坏 BEM 平级原则 */ } } - 真正该警惕的是
&--modifier &__element这类嵌套:它生成的不是 BEM 类名,而是 CSS 选择器组合,既难 debug,又破坏命名即作用域的原则
真正难的不是记住规则,而是在写第 17 个 menu__item--active 时,还能忍住不写成 menu-item-active——因为少敲两个字符的代价,是下次别人 grep menu-item 时,根本不知道它属于哪个模块。


















