BEM 命名规范深度解析:让 CSS 类名自我表达

1 阅读8分钟

BEM 命名规范深度解析:让 CSS 类名自我表达

BEM 是前端开发中最实用的 CSS 命名规范之一。本文从命名之痛出发,深入剖析 BEM 的三大核心概念,结合微信按钮实战案例,带你彻底掌握这套国际通用的命名体系。

前言

每个前端开发者都经历过这样的痛苦:

.box { }
.box-title { }
.box-content { }
.box-content-item { }
.box-content-item-active { }

类名越来越长,结构越来越乱,维护越来越困难。更糟糕的是,团队协作时每个人有自己的命名习惯,代码风格千差万别。

BEM 就是解决这个问题的银弹。它简单、直观、国际通用,是前端工程化的基石之一。


一、为什么需要 BEM?

1.1 没有规范的命名灾难

假设你在写一个文章列表页面:

<!-- 开发者 A 的写法 -->
<div class="article-list">
    <div class="item">
        <div class="title">标题</div>
        <div class="desc">描述</div>
    </div>
</div>

<!-- 开发者 B 的写法 -->
<div class="articles">
    <div class="article-item">
        <h2 class="article-title">标题</h2>
        <p class="article-desc">描述</p>
    </div>
</div>

<!-- 开发者 C 的写法 -->
<div class="list">
    <div class="list-item">
        <div class="list-item-title">标题</div>
        <div class="list-item-desc">描述</div>
    </div>
</div>

同一个页面,三种完全不同的命名风格。没有规范,代码就是一团乱麻。

1.2 BEM 的核心理念

BEM 让类名即结构。看到类名,就能知道它在页面中的位置和作用:

<div class="article">
    <div class="article__item">
        <h2 class="article__title">标题</h2>
        <p class="article__desc">描述</p>
    </div>
</div>

一眼就能看出:article 是块,article__item 是元素,article__title 也是元素。


二、BEM 的三大核心概念

BEM 是 Block(块)、Element(元素)、Modifier(修饰符) 的缩写。

2.1 Block:独立的块

Block 是一个独立的、可复用的组件。它可以是一个按钮、一个导航栏、一个卡片,甚至是一个完整的页面。

特征

  • 独立存在,不依赖其他元素
  • 可以在页面任何地方复用
  • 命名用简单的英文单词
<!-- Block:按钮 -->
<button class="btn">点击</button>

<!-- Block:卡片 -->
<div class="card">
    <img class="card__img" src="...">
    <h3 class="card__title">标题</h3>
    <p class="card__desc">描述</p>
</div>

<!-- Block:页面 -->
<div class="page">
    <header class="page__hd">...</header>
    <main class="page__bd">...</main>
</div>

2.2 Element:块的元素

Element 是 Block 的一部分,不能独立存在。它用 双下划线 __ 与 Block 连接。

特征

  • 属于某个 Block,不能单独使用
  • 命名格式:block__element
<div class="card">                    <!-- Block -->
    <img class="card__img">           <!-- Element -->
    <h3 class="card__title">          <!-- Element -->
        <span class="card__tag">标签</span>  <!-- Element -->
    </h3>
    <p class="card__desc">...</p>     <!-- Element -->
</div>

常见错误

<!-- ❌ 错误:Element 命名嵌套 -->
<div class="card">
    <div class="card__header">
        <h3 class="card__header__title">标题</h3>  <!-- 错误!命名中出现了 __ __ -->
    </div>
</div>

<!-- ✅ 正确:Element 直接属于 Block -->
<div class="card">
    <div class="card__header">
        <h3 class="card__title">标题</h3>  <!-- 正确 -->
    </div>
</div>

BEM 命名规范不允许 __ 嵌套 __。所有 Element 都是 Block 的直接子元素,层级通过命名体现,而不是通过 __ 嵌套。

2.3 Modifier:修饰符

Modifier 表示 Block 或 Element 的状态、变体或外观。它用 双中划线 -- 连接。

特征

  • 表示某种状态(禁用、选中、激活)
  • 表示某种变体(大小、颜色、主题)
  • 命名格式:block--modifierblock__element--modifier
<!-- Modifier:按钮的不同状态 -->
<button class="btn btn--primary">主要按钮</button>
<button class="btn btn--default">次要按钮</button>
<button class="btn btn--disabled">禁用按钮</button>
<button class="btn btn--loading">加载中</button>

<!-- Modifier:卡片的不同大小 -->
<div class="card card--large">大卡片</div>
<div class="card card--small">小卡片</div>

Modifier 与 Block 同时出现

Modifier 不能单独使用,必须与 Block 或 Element 一起:

<!-- ❌ 错误:Modifier 单独使用 -->
<button class="btn--primary">点击</button>

<!-- ✅ 正确:Modifier 配合 Block -->
<button class="btn btn--primary">点击</button>

三、标准 BEM vs 微信命名风格:一个重要区分

在深入实战之前,必须先澄清一个常见的混淆点

3.1 标准 BEM 的符号规则

符号标准 BEM用途
__✅ 双下划线连接 Block 和 Element
--✅ 双中划线连接 Block/Element 和 Modifier

标准 BEM 示例

<button class="btn btn--primary">主要按钮</button>
<button class="btn btn--default">次要按钮</button>
.btn--primary { background-color: #07c160; }
.btn--default { background-color: #f0f0f0; }

3.2 微信的命名风格:单下划线 _

微信团队在实际项目中使用的是单下划线 _,而不是标准 BEM 的双中划线 --

<!-- 微信实际代码 -->
<a class="weui-btn weui-btn_primary">主要按钮</a>
<a class="weui-btn weui-btn_default">次要按钮</a>
/* 微信实际 CSS */
.weui-btn_primary { background-color: #07c160; }
.weui-btn_default { background-color: rgba(0, 0, 0, 0.1); }

3.3 为什么会有这种差异?

对比项标准 BEM微信风格
Elementblock__elementblock__element ✅ 一致
Modifierblock--modifierblock_modifier ❌ 不同
历史原因Yandex 提出微信团队早期约定
兼容性现代浏览器早期兼容 IE 的考虑

关键点__ 部分两者是一致的,差异只在 Modifier 的符号上。微信用 _,标准 BEM 用 --

3.4 你应该用哪种?

推荐:遵循标准 BEM(--

原因:

  1. 国际通用:标准 BEM 是全球前端社区广泛认可的规范
  2. 语义明确-- 一眼就能看出是 Modifier,_ 容易和 Element 混淆
  3. 工具支持:大多数 CSS 预处理器和代码检查工具默认支持标准 BEM
  4. 团队共识:新成员更容易理解

但在微信生态开发时:如果团队已经使用微信风格(_),保持一致性更重要。

3.5 实战:用标准 BEM 重写微信按钮

<!-- 标准 BEM 写法 -->
<div class="page">
    <header class="page__hd">
        <h1 class="page__title">这是一个页面</h1>
        <p class="page__desc">这是一个页面的描述</p>
    </header>
    <main class="page__bd">
        <div class="button-sp-area">
            <a class="weui-btn weui-btn--primary">主要按钮</a>
            <a class="weui-btn weui-btn--default">次要按钮</a>
            <a class="weui-btn weui-btn--default">次要按钮</a>
        </div>
    </main>
</div>
/* Block:页面 */
.page {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}

/* Element:页面头部 */
.page__hd {
    padding: 40px;
}

/* Element:页面标题 */
.page__title {
    font-size: 20px;
    font-weight: 400;
}

/* Element:页面描述 */
.page__desc {
    margin-top: 4px;
    color: rgba(0, 0, 0, 0.45);
    font-size: 14px;
}

/* Block:按钮 */
.weui-btn {
    display: block;
    width: 184px;
    margin: 0 auto;
    padding: 12px 24px;
    font-size: 17px;
    text-align: center;
    border-radius: 8px;
}

/* Modifier:主要按钮(标准 BEM:--) */
.weui-btn--primary {
    background-color: #07c160;
    color: #fff;
}

/* Modifier:次要按钮(标准 BEM:--) */
.weui-btn--default {
    color: rgba(0, 0, 0, 0.9);
    background-color: rgba(0, 0, 0, 0.1);
}

3.6 命名设计思路

为什么选择 page__hd 而不是 page__header

BEM 推荐使用简短的缩写,保持命名简洁:

完整单词BEM 缩写说明
headerhd头部
bodybd主体
footerft底部
titletitle标题(已够短)
descriptiondesc描述

为什么选择 weui-btn 而不是 btn

weui项目前缀,表示这个按钮属于微信的 UI 规范。在大型项目中,前缀可以避免命名冲突:

.weui-btn { }      /* 微信按钮 */
.ant-btn { }       /* Ant Design 按钮 */
.el-btn { }        /* Element UI 按钮 */

四、BEM 的四大优势

4.1 结构清晰

看到类名就知道层级关系:

<div class="article">
    <div class="article__header">
        <h1 class="article__title">标题</h1>
        <span class="article__date">2024-01-01</span>
    </div>
    <div class="article__body">
        <p class="article__paragraph">正文...</p>
    </div>
    <div class="article__footer">
        <button class="article__btn article__btn--share">分享</button>
        <button class="article__btn article__btn--like">点赞</button>
    </div>
</div>

不需要看 HTML 结构,只看类名就能画出页面结构图。

4.2 命名简单

不需要想复杂的英文单词,结构即命名:

/* ❌ 没有 BEM:命名越来越长 */
.page-header { }
.page-header-title { }
.page-header-description { }
.page-content { }
.page-content-button { }
.page-content-button-primary { }

/* ✅ 使用 BEM:命名简洁清晰 */
.page { }
.page__hd { }
.page__title { }
.page__desc { }
.page__bd { }
.btn { }
.btn--primary { }

4.3 无嵌套依赖

BEM 的类名是扁平的,不依赖 HTML 嵌套结构:

/* ❌ 传统嵌套:依赖 HTML 结构 */
.card .title { }        /* 只有 .card 里的 .title 才生效 */
.card .content p { }    /* 层级太深,性能差 */

/* ✅ BEM:扁平命名,无嵌套依赖 */
.card__title { }        /* 任何地方的 .card__title 都生效 */
.card__content { }      /* 扁平,性能更好 */

4.4 团队协作

BEM 是国际规范,所有人都能理解:

<!-- 新人看到这段代码,立刻明白结构 -->
<div class="nav">
    <a class="nav__item nav__item--active">首页</a>
    <a class="nav__item">产品</a>
    <a class="nav__item">关于</a>
</div>

不需要注释,类名本身就是最好的注释。


五、BEM 常见误区

5.1 误区一:Element 嵌套 Element

<!-- ❌ 错误 -->
<div class="card">
    <div class="card__header">
        <h3 class="card__header__title">标题</h3>
    </div>
</div>

<!-- ✅ 正确 -->
<div class="card">
    <div class="card__header">
        <h3 class="card__title">标题</h3>
    </div>
</div>

5.2 误区二:Modifier 单独使用

<!-- ❌ 错误 -->
<button class="btn--primary">点击</button>

<!-- ✅ 正确 -->
<button class="btn btn--primary">点击</button>

5.3 误区三:用 Element 表示嵌套层级

<!-- ❌ 错误:试图用命名表示层级 -->
<div class="page">
    <div class="page__content">
        <div class="page__content__sidebar">
            <div class="page__content__sidebar__menu">...</div>
        </div>
    </div>
</div>

<!-- ✅ 正确:每个都是独立的 Block -->
<div class="page">
    <div class="page__content">
        <aside class="sidebar">
            <nav class="menu">...</nav>
        </aside>
    </div>
</div>

当结构变得复杂时,应该拆分为多个独立的 Block,而不是用 Element 嵌套。


六、BEM 与 CSS 预处理器

6.1 SCSS 中的 BEM

SCSS 的嵌套语法让 BEM 写起来更优雅:

// Block
.card {
    padding: 20px;
    border-radius: 8px;

    // Element
    &__img {
        width: 100%;
        height: 200px;
    }

    &__title {
        font-size: 18px;
        font-weight: bold;
    }

    &__desc {
        color: #666;
    }

    // Modifier
    &--large {
        padding: 40px;
    }

    &--small {
        padding: 10px;
    }
}

编译后:

.card { padding: 20px; border-radius: 8px; }
.card__img { width: 100%; height: 200px; }
.card__title { font-size: 18px; font-weight: bold; }
.card__desc { color: #666; }
.card--large { padding: 40px; }
.card--small { padding: 10px; }

6.2 避免过度嵌套

// ❌ 过度嵌套:编译后选择器太长
.card {
    .card__header {
        .card__title {
            .card__icon { }
        }
    }
}

// ✅ 扁平嵌套:保持 BEM 的简洁
.card {
    &__header { }
    &__title { }
    &__icon { }
}

七、BEM 实战:构建一个完整的组件

7.1 需求:一个可复用的卡片组件

<!-- 基础卡片 -->
<div class="card">
    <img class="card__img" src="avatar.jpg">
    <div class="card__body">
        <h3 class="card__title">文章标题</h3>
        <p class="card__desc">文章描述...</p>
    </div>
    <div class="card__footer">
        <span class="card__date">2024-01-01</span>
        <button class="card__btn card__btn--primary">阅读更多</button>
    </div>
</div>

<!-- 大图卡片 -->
<div class="card card--large">
    <img class="card__img" src="banner.jpg">
    <div class="card__body">
        <h3 class="card__title">大图文章标题</h3>
        <p class="card__desc">大图文章描述...</p>
    </div>
</div>

<!-- 横向卡片 -->
<div class="card card--horizontal">
    <img class="card__img" src="thumb.jpg">
    <div class="card__body">
        <h3 class="card__title">横向文章标题</h3>
        <p class="card__desc">横向文章描述...</p>
    </div>
</div>

7.2 CSS 实现

/* Block:卡片 */
.card {
    display: flex;
    flex-direction: column;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* Element:图片 */
.card__img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

/* Element:内容区 */
.card__body {
    padding: 16px;
}

/* Element:标题 */
.card__title {
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 8px;
}

/* Element:描述 */
.card__desc {
    font-size: 14px;
    color: #666;
    line-height: 1.5;
}

/* Element:底部 */
.card__footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    border-top: 1px solid #eee;
}

/* Element:按钮 */
.card__btn {
    padding: 8px 16px;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
}

/* Modifier:主要按钮 */
.card__btn--primary {
    background-color: #07c160;
    color: #fff;
}

/* Modifier:次要按钮 */
.card__btn--default {
    background-color: #f0f0f0;
    color: #333;
}

/* Modifier:大图卡片 */
.card--large .card__img {
    height: 300px;
}

.card--large .card__title {
    font-size: 24px;
}

/* Modifier:横向卡片 */
.card--horizontal {
    flex-direction: row;
}

.card--horizontal .card__img {
    width: 120px;
    height: 120px;
}

八、BEM 命名速查表

类型格式示例说明
Block.block.btn.card.nav独立组件
Element.block__element.btn__icon.card__title组件的一部分
Modifier.block--modifier.btn--primary.card--large状态或变体
Element + Modifier.block__element--modifier.nav__item--active元素的状态

九、总结

BEM 是前端开发中最简单、最实用的命名规范:

  1. Block:独立的组件,用简单单词命名
  2. Element:组件的一部分,用 __ 连接
  3. Modifier:状态或变体,用 -- 连接
  4. 扁平结构:不嵌套 Element,保持类名简洁
  5. 团队协作:国际规范,所有人都能理解

掌握 BEM,你的 CSS 类名将自我表达,代码将清晰可维护,团队协作将高效顺畅


命名是编程中最难的事情之一。BEM 让命名变得简单,让代码变得优雅。

标签CSS BEM 命名规范 前端工程化 最佳实践 微信UI