CSS 架构与 BEM:可维护的命名约定

FreeGuideOnline 最新 2026-06-15

引言:为什么你的 CSS 总是一团乱麻?

你是否曾面对过一个几千行的样式文件,修改一个按钮颜色却担心牵一发而动全身?或者在项目迭代半年后,面对自己写的类名感到陌生和困惑?这些问题并非源于 CSS 本身的难度,而是缺乏一套清晰、可维护的架构思想命名规范

本教程将带你深入 BEM 方法论,它是解决上述问题的经典方案,被全球无数团队验证有效。我们将从 CSS 架构的核心痛点出发,逐步掌握 BEM 的原理、命名规则、最佳实践以及在现代工程中的灵活应用。

CSS 架构的核心目标:可维护性

在讨论 BEM 之前,我们需要先明确“好的 CSS 架构”应该实现什么。它不关心你写了多少动画或渐变,只聚焦于代码的长期健康度

  • 可预测性:改动一个组件的样式,不会意外影响到其他不相关的组件。
  • 可复用性:组件可以在不同上下文中使用,无需重复编写样式。
  • 可扩展性:新增功能或变体时,无需重写已有代码。
  • 协作友好:团队成员阅读代码时,能快速理解各层级关系,且命名不易冲突。

CSS 本身的全局作用域、层叠和继承机制,很容易让样式变得难以控制。架构方法论和命名约定正是为了驯服这些原生特性,把它们关进可控的笼子。

BEM 概述:不止是命名,更是思想

BEM 是三个单词的缩写:Block(块)、Element(元素)、Modifier(修饰符)。它起源于俄罗斯搜索巨头 Yandex,并随着开源项目传播,成为一种流行的组件化 CSS 思维模型。

BEM 的核心思想是:将用户界面拆分成一系列独立的、可复用的,块内部可以包含元素,块或元素的状态与外观变化通过修饰符来管理。这套方法论强制约定了:

  • 类名即文档:类名清晰表达了各部分的结构与作用。
  • 平铺选择器:严格避免嵌套选择器,保证所有样式的权重几乎一致。
  • 组件独立:一个块的外观不应依赖于其所处的外部容器位置。

深入 BEM 三要素

1. Block(块)

是页面中一个功能独立的、可复用的组件,它在逻辑和样式上都是一个完整单元。块可以嵌套在其他块中,但自身保持独立。

  • 特征:块名是一个有意义的词或词组,描述它“是什么”(如 headersearch-formbutton),而不是描述它“看起来如何”(如 red-textbig-button)。
  • 示例
    • .menu
    • .card
    • .login-form

一个块可以使用任意标签,但建议保持语义化。块的定位(margin、position)通常应由其父级上下文控制,而非块自身,以保证复用性。

2. Element(元素)

元素是块的组成部分,自身不能脱离块独立存在。元素描述的是它在块内的角色,而不是具体的视觉样式。

  • 命名规则block__element,使用双下划线 __ 连接块名与元素名。
  • 示例
    • .menu__item
    • .card__title
    • .login-form__input

元素可以嵌套,但 BEM 的命名必须保持扁平化!这意味着你不应该写出 .block__elem1__elem2 这样的类名。无论 DOM 结构如何嵌套,元素的所有权只能属于最初的那个块。正确的做法是:.block__elem2 直接表达它是块 block 的元素,至于它在 DOM 中是包裹在 elem1 内还是直接放在块内,并不影响命名。

3. Modifier(修饰符)

修饰符用来定义块或元素的外观、状态或行为的变体。比如一个按钮是主要样式还是危险样式,一个菜单项是否被选中等。

  • 命名规则:使用双连字符 --,形式为 block--modifierblock__element--modifier
  • 常见类型
    • 布尔类型:表示一个状态是否存在,如 .button--disabled.menu__item--active
    • 键值类型:当修饰符有多种取值时,形式为 block--key_valuecolor_red 风格,例如 .button--size_large.theme_forest。这里建议使用单个字符 _ 连接键值,以区别于双连字符的修饰符分隔符,但很多团队直接采用 block--modifier-value 的连字符长形式,如 .button--size-large,更方便阅读。只要团队内部统一即可。

关键准则:永远不要单独使用修饰符类。在 HTML 中,修饰符类必须追加在基础块或元素类之后,比如 <button class="button button--primary">。这保证了无修饰符时依然是带有完整基础样式的组件。

BEM 实战:一个卡片组件

让我们用 BEM 实现一个常见的“文章卡片”组件,包括标题、图像、摘要以及一个“推荐”标记的变体。

HTML 结构

<div class="card card--featured">
  <img class="card__image" src="image.jpg" alt="文章封面">
  <div class="card__content">
    <h3 class="card__title">CSS 架构与 BEM</h3>
    <p class="card__excerpt">构建可维护样式的核心方法。</p>
    <button class="card__button card__button--primary">阅读更多</button>
  </div>
</div>

CSS 样式(部分):

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

/* 元素 */
.card__image {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.card__content {
  padding: 1.2rem;
  display: flex;
  flex-direction: column;
  flex-grow: 1;
}

.card__title {
  font-size: 1.25rem;
  margin-bottom: 0.5rem;
  color: #222;
}

.card__excerpt {
  flex-grow: 1;
  color: #555;
  margin-bottom: 1rem;
}

.card__button {
  align-self: flex-start;
  padding: 0.5em 1.2em;
  border: 1px solid #ccc;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
}

/* 元素修饰符 */
.card__button--primary {
  background: #0066cc;
  color: white;
  border-color: #0066cc;
}

/* 块修饰符:.card--featured */
.card--featured {
  border-left: 4px solid #0066cc;
  background: #f0f6ff;
}

观察这段代码:

  • 所有选择器都是一个类名,没有类型选择器(h3 等)或后代选择器(.card .title),全平级权重。
  • 即使 .card__button 位于 .card__content 内部,它也直接用 .card__button 表达,而不是 .card__content__button
  • 变体通过类名组合实现(card card--featured),语义清晰。

BEM 最佳实践与常见误区

1. 避免过度嵌套的类名

❌ 错误:.block__el1__el2
✅ 正确:.block__el2
永远记住,每个元素在 BEM 中只属于一个块,命名只反映块-元素两级关系。

2. 不要用修饰符描述元素

修饰符只能用于块或元素,不能创造一个“元素修饰符”再去产生新元素。例如,没有 .block__el--modifier__child 这种写法。如果因为修饰符带来了一个新的子元素,应当将其归为块或元素的一部分,并用额外的类控制外观。

3. 不要只依赖类名,也应注意 HTML 结构的清洁

BEM 不意味着你需要给每一个 HTML 标签一个类名。如果某个 div 纯粹是为了布局包裹,并且没有特定样式需要挂钩,它可以没有类。BEM 只对需要样式或逻辑的节点命名。

4. 修饰符的键值命名一致性

团队应约定键值形式。推荐以下两种之一:

  • 双连字符加完整词组:block--theme-darkblock--size-large
  • 双连字符加短横分隔:block--theme_dark (表示键为 theme,值为 dark)

在现代项目中使用 BEM

结合预处理器(SCSS)

SCSS 的父选择器 & 可以大幅减少 BEM 书写时的重复,让代码更简洁:

.card {
  // 块样式
  &__title {
    font-size: 1.25rem;
  }
  &__button {
    border: 1px solid #ccc;
  
    &--primary {
      background: blue;
    }
  }
  &--featured {
    border-left: 4px solid blue;
  }
}

在 CSS Modules 或 CSS-in-JS 中的应用

即使使用 CSS Modules 生成局部作用域类名,BEM 的方法论依然有指导意义。你可以用 styles.cardstyles.title 这样的命名,但内部逻辑仍遵循“组件-元素-状态”的划分,使组件内部样式组织清晰。

BEM 与工具类 (Utility-first) 的配合

BEM 并非与 Tailwind CSS 等工具类框架对立。你可以将 BEM 用于主要组件的结构性和状态性样式,而使用工具类处理细微的间距、颜色调整。例如:

<div class="card card--featured">
  <h3 class="card__title text-lg font-bold">标题</h3>
  <p class="card__excerpt text-gray-600">描述</p>
</div>

核心原则不变:BEM 管理组件边界,工具类辅助微调。

BEM 解决的实际问题

问题 BEM 的应对
样式冲突与意外覆盖 所有类名全平级,无嵌套,杜绝权重灾难
不知道某个节点是否可以修改 类名直接告知其归属:search-form__input 即搜索表单下的输入元素
组件状态管理混乱 --active--disabled 等让状态显性化
删除旧代码时犹豫不决 基于组件块搜索相关类名,可安全移除

小结与行动清单

BEM 不是一颗银弹,但它是一套经过大规模工程考验的、简单且有效的 CSS 组织方法论。它强迫你思考组件的封装边界,从而写出更可维护的代码。

立即开始实践:

  1. 选择一个现有组件,将其样式改写成 BEM 命名。
  2. 制定团队规范:确定双连字符 -- 为修饰符,下划线 __ 为元素,并约定键值修饰符的写法。
  3. 与预处理器结合,利用 & 减少重复。
  4. Code Review 时关注有没有深度嵌套的类名或不合理的修饰符。

当你的 CSS 变得像乐高积木一样可以自由组合而不怕倒塌时,你就真正理解了 CSS 架构的魅力。