BEM:CSS 命名规范

介绍

BEM:Block(块)‑Element(元素)‑Modifier(修饰符),是一套组件化 CSS 命名约定,解决类名冲突、样式耦合、语义不清晰问题,适合大型项目。

一、概念

1. Block 块

独立、可复用的组件块,代表一个整体模块,不依赖其他组件。

  • 命名:使用小写字母,连字符 - 连接多单词
  • 特征:可以独立使用

示例:

/* 块:卡片、按钮、导航栏 */
.card {}
.nav-bar {}
.search-form {}

✅正确:.user-card
❌错误:.UserCard(不要驼峰)、.userCard(不要小驼峰)

2. Element 元素

块内部的子部分,不能脱离块单独使用。

  • 语法:block__element,使用两个下划线 __ 分隔块和元素
  • 含义:属于某个块的子元素,脱离块就没有意义
.card__title {}    /* card块里的标题 */
.card__content {}  /* card块里的内容 */
.card__btn {}      /* card块里的按钮 */

.nav-bar__item {}  /* 导航栏的菜单项 */

不允许多层嵌套 block__elem1__elem2!
❌错误:.card__header__title
✅正确:.card__header-title(多单词用单横线,只用一次__)

3. Modifier 修饰符

用来描述块/元素的状态、变体、外观(尺寸、颜色、激活、禁用、选中)。

  • 语法:block--modifier 或者 block__element--modifier,两个短横线 --
  • 修饰符本身不单独存在,必须依附块或元素
/* 块的修饰符 */
.card--dark {}        /*深色版本卡片*/
.btn--primary {}      /*主按钮*/
.btn--disabled {}     /*禁用状态按钮*/

/*元素的修饰符*/
.nav-bar__item--active {} /*导航激活项*/
.card__btn--large {}      /*卡片内大按钮*/

二、语法

块:block-name
元素:block-name__element-name
修饰块:block-name--modifier-name
修饰元素:block-name__element-name--modifier-name

HTML示例完整演示

<!-- BEM示例:卡片组件 -->
<div class="card card--dark">
  <div class="card__header">
    <h3 class="card__title">标题</h3>
  </div>
  <div class="card__content">内容文本</div>
  <button class="card__btn card__btn--primary">确定</button>
</div>
/*块*/
.card {
  border:1px solid #eee;
}
/*块修饰符*/
.card--dark {
  background:#222;
  color:#fff;
}
/*元素*/
.card__header {
  padding:10px;
}
.card__title {
  font-size:16px;
}
.card__btn {
  padding:6px 12px;
}
/*元素修饰符*/
.card__btn--primary {
  background:#1677ff;
  color:#fff;
}

关键点:修饰符类永远和基础类同时写在标签上,不要单独写修饰符类。

❌ <div class="card--dark">
✅ <div class="card card--dark">


三、命名多单词规则

  • 块、元素、修饰符内部多个单词:用单横线 -
  • 块与元素分隔:双下划线 __
  • (块|元素)与修饰符分隔:双横线 --

示例:

.user-profile          块:用户资料
.user-profile__avatar  元素:用户资料的头像
.user-profile__avatar--large 修饰:大尺寸头像

四、BEM常见误区

1. ❌不要无限嵌套__

//错误
.card__header__title{}
//正确
.card__header-title{}

2. ❌修饰符不能单独使用

修饰符只是追加样式,基础类必须保留。

3. ❌不要把页面结构当块

块是可复用组件,不是页面布局容器;页面布局可以单独写布局类。

4. ❌不要 DOM 结构和 BEM 强行绑定

BEM是组件语义,不是html层级;DOM嵌套深不代表要多加__。

5. ✅允许块嵌套另一个块

一个块内部可以放另一个独立块:

<div class="card">
  <div class="search-form"></div> <!-- search-form是独立块,不用写card__search-form -->
</div>

如果这个search-form只是card内部专属、不能单独拿出去用,才写成card__search-form。


五、BEM的优缺点

✅优点

  1. 语义化:看类名就知道属于哪个组件,是什么角色,什么状态
  2. 避免选择器冲突,减少权重滥用,少用!important
  3. 组件化,方便复制、迁移、复用组件
  4. 便于团队协作,统一命名习惯,大项目维护友好
  5. 和css module、scss配合非常适配

❌缺点

  1. 类名很长,html标签会比较臃肿
  2. 新手容易滥用__,过度嵌套命名
  3. 需要严格遵守约定,团队要统一认知

六、DOM多层嵌套的写法

HTML嵌套,跟BEM的__没关系!DOM嵌套 ≠ BEM嵌套,__只能出现一次,不能写 a__b__c。

场景举例:DOM是三层嵌套

<div class="card">          <!-- block:card -->
  <div class="card__head">  <!-- element -->
    <div>
      <h2>标题</h2>        <!-- DOM深层嵌套 -->
    </div>
  </div>
</div>

❌错误写法(跟着DOM层级多加__)

.card__head__title {} /*禁止双下划线叠加*/

✅正确:元素内部多单词用单横线 -

<div class="card">
  <div class="card__head">
    <div>
      <h2 class="card__head-title">标题</h2>
    </div>
  </div>
</div>
.card {}
.card__head {}
.card__head-title {} /* 一次__,后面用单横线 */

规则:block__xxx-yyy

  • __:仅1次,分割块和第一级子元素
  • 后面再细分,全部用普通 -,不要再加 __

案例2:更深DOM层级例子

HTML DOM结构(4层标签嵌套)

<div class="modal">
  <div class="modal__wrapper">
    <div class="modal__wrapper-inner">
      <div class="modal__wrapper-inner-content">
        <h3 class="modal__wrapper-inner-title">弹窗标题</h3>
      </div>
    </div>
  </div>
</div>

CSS:

/* block */
.modal {}

/* element,__只写一次,后面全部单横线 */
.modal__wrapper {}
.modal__wrapper-inner {}
.modal__wrapper-inner-content {}
.modal__wrapper-inner-title {}

注意: modal__wrapper-inner-title 不要写成 modal__wrapper-inner-content-title,不需要把完整 DOM 路径全部拼接进类名。类名超长,DOM 改一层,类名就要大改,维护灾难。

两种嵌套区分:「组件内部子元素」vs「嵌入另一个独立组件」

情况A:这个子模块只属于父块,拿出去不能用 → 用 block__xxx-yyy

<div class="card">
  <div class="card__footer">
    <button class="card__footer-btn">按钮</button>
  </div>
</div>
.card {}
.card__footer {}
.card__footer-btn {}

情况B:里面是完全独立组件,可以拿出来到处复用 → 不要再加父块前缀,直接用自己的block

❌不要写成 .card__button

✅直接写 .button

<div class="card">
  <div class="card__footer">
    <button class="button button--primary">确认</button>
  </div>
</div>
.card {}
.card__footer {}
.button {}
.button--primary {}

❌禁止操作汇总

  1. 禁止多个__

    /* 错 */
    .modal__a__b__c {}
    /* 对 */
    .modal__a-b-c {}
    
  2. 不要用后代选择器去规避BEM(很多人图省事这么写,BEM不推荐)

    /* 不推荐,增加权重,破坏BEM扁平思想 */
    .modal .modal__wrapper .title {}
    /* 推荐:直接给标签挂类名 */
    .modal__wrapper-title {}
    

BEM思想:尽量给需要样式的DOM节点直接挂class,少依赖DOM层级关系选择样式,就算HTML嵌套很深,每个需要样式的节点直接写完整BEM类名。

七、好处

解决大型项目 CSS 混乱、选择器权重打架、命名冲突、组件不好复用的问题。

  1. 类名自带语义,看 class 就知道是什么
  2. 彻底减少样式冲突
  3. CSS 选择器扁平,权重可控,少用 !important
  4. 组件可复制、迁移复用
  5. HTML 结构改变,CSS 不容易坏
  6. 团队协作统一,降低沟通成本
  7. 和 CSS Module / Vue scoped / Tailwind 之外的传统项目适配很好

总结

  • 块内单词单横杠,块元之间双下划;
  • 状态变体双横杠,修饰永远不独行;
  • 下划线只准一次,独立块不要套进元素。
  • HTML随便嵌套几层,BEM的__最多出现一次;
  • 后续细分全部 -;
  • 内部如果是独立组件,直接用自己的块,不要加父块前缀。