CSS命名规范:BEM与其他方法论

工具相关 ·

CSS命名是前端开发中最具争议的话题之一。一个好的命名规范可以让代码自解释、易于维护、避免样式冲突。从早期的随机命名到如今的 BEM、OOCSS、SMACSS 等方法论,CSS 命名经历了漫长的演进过程。本文将全面介绍主流的CSS命名规范,帮助团队选择最适合自己的方法论。

一、CSS命名的挑战

1.1 为什么CSS命名这么难?

CSS命名之所以困难,是因为它面临独特的挑战:

挑战说明
全局作用域CSS类名默认是全局的,任何地方都可以被覆盖
选择器优先级命名不当可能导致优先级混乱
样式冲突同名类在不同组件中可能产生冲突
可预测性类名应该能够预测其作用和影响范围
语义化类名应该传达其含义,而不是外观
可维护性随着项目增长,类名管理变得越来越复杂

1.2 糟糕命名的后果

/* 糟糕的命名示例 */
.btn {}          /* 太泛化,不知道是什么按钮 */
.red {}          /* 以外观命名,难以维护 */
.wrapper {}      /* 到处都有 wrapper,不知道哪个是哪个 */
.active {}       /* 太通用,容易冲突 */
.left {}         /* 含义不明确 */
.header {}       /* 多个页面都有 header,如何区分? */
.mt10 {}         /* 无语义的缩写 */
.box1 {}         /* 数字命名,毫无意义 */
.clearfix {}     /* 实现细节暴露在命名中 */

二、BEM命名方法论

2.1 BEM简介

BEM(Block-Element-Modifier)是最流行的CSS命名方法论之一,由Yandex团队提出。它的核心理念是将用户界面划分为独立的"块"。

BEM = Block + Element + Modifier

组成部分英文说明示例
Block块独立的、可复用的组件header, card, menu
Element元素块的组成部分,不能独立存在title, item
Modifier修饰符块或元素的变体或状态--active, --large

2.2 BEM命名规则

BlockName__ElementName--ModifierName
  • Block 和 Element 之间用双下划线 __ 连接
  • Block/Element 和 Modifier 之间用双横线 -- 连接
  • 所有名称使用小写字母和连字符(kebab-case)

2.3 BEM完整示例

/* Block: 搜索表单 */
.search-form {
  display: flex;
  padding: 16px;
  background-color: #f5f5f5;
}

/* Element: 输入框 */
.search-form__input {
  flex: 1;
  padding: 8px 12px;
  font-size: 14px;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
}

/* Element: 按钮 */
.search-form__button {
  padding: 8px 20px;
  font-size: 14px;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}

/* Modifier: 按钮-主要样式 */
.search-form__button--primary {
  color: #fff;
  background-color: #007bff;
}

/* Modifier: 按钮-禁用状态 */
.search-form__button--disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Modifier: 搜索表单-暗色主题 */
.search-form--dark {
  background-color: #333;
}

/* Element: 搜索表单-暗色主题下的输入框 */
.search-form--dark .search-form__input {
  background-color: #555;
  border-color: #666;
  color: #fff;
}

对应的HTML:

<form class="search-form search-form--dark">
  <input class="search-form__input" type="text" placeholder="搜索...">
  <button class="search-form__button search-form__button--primary">
    搜索
  </button>
</form>

<form class="search-form">
  <input class="search-form__input" type="text" placeholder="搜索...">
  <button class="search-form__button search-form__button--disabled" disabled>
    搜索
  </button>
</form>

2.4 BEM的嵌套规则

BEM 不支持深层嵌套,这是它的一个重要原则:

/* 错误:不要在 BEM 中嵌套 Element */
.card__header__title { }

/* 正确:扁平化命名 */
.card__header-title { }

/* 或者将 header-title 作为独立的 Element */
.card__title { }

2.5 BEM的优缺点

优点缺点
命名空间清晰,避免冲突类名较长,书写繁琐
一眼就能看出组件结构学习曲线稍高
优先级扁平化,易于覆盖HTML中需要写很多类名
便于搜索和定位不适合频繁变化的样式
与JavaScript配合良好过度使用时导致类名爆炸

三、OOCSS(面向对象CSS)

3.1 OOCSS简介

OOCSS(Object Oriented CSS)由 Nicole Sullivan 提出,核心思想是将CSS当作对象来处理,强调结构和皮肤的分离、容器和内容的分离。

3.2 OOCSS的两大原则

原则一:分离结构和皮肤

/* 结构(Structure)- 定义尺寸、布局等 */
.box {
  padding: 16px;
  margin-bottom: 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

/* 皮肤(Skin)- 定义视觉样式 */
.box--primary {
  background-color: #007bff;
  color: #fff;
  border-color: #007bff;
}

.box--success {
  background-color: #28a745;
  color: #fff;
  border-color: #28a745;
}

.box--warning {
  background-color: #ffc107;
  color: #333;
  border-color: #ffc107;
}

原则二:分离容器和内容

/* 不好的做法:内容样式依赖于容器 */
.sidebar .title {
  font-size: 18px;
  color: #333;
}

.sidebar .title .icon {
  margin-right: 8px;
}

/* 好的做法:内容样式独立定义 */
.title {
  font-size: 18px;
  color: #333;
}

.title__icon {
  margin-right: 8px;
}

/* 使用时组合 */
<div class="sidebar">
  <h2 class="title">
    <span class="title__icon">📌</span>
    标题
  </h2>
</div>

3.3 OOCSS 实用对象示例

/* 媒体对象 - 经典的OOCSS模式 */
.media {
  display: flex;
  align-items: flex-start;
}

.media__img {
  margin-right: 16px;
  flex-shrink: 0;
}

.media__body {
  flex: 1;
}

/* 使用方法 */
<div class="media">
  <img class="media__img" src="avatar.jpg" alt="头像">
  <div class="media__body">
    <p>这是媒体对象的内容区域。</p>
  </div>
</div>

/* 尺寸变体 */
.media--large .media__img {
  width: 80px;
  height: 80px;
}

.media--small .media__img {
  width: 40px;
  height: 40px;
}

/* 通用按钮对象 */
.btn {
  display: inline-block;
  padding: 8px 16px;
  font-size: 14px;
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  text-align: center;
}

/* 按钮皮肤 */
.btn--primary { background-color: #007bff; color: #fff; }
.btn--secondary { background-color: #6c757d; color: #fff; }
.btn--danger { background-color: #dc3545; color: #fff; }

/* 按钮尺寸 */
.btn--sm { padding: 4px 8px; font-size: 12px; }
.btn--lg { padding: 12px 24px; font-size: 18px; }

四、SMACSS(可扩展模块化CSS架构)

4.1 SMACSS简介

SMACSS(Scalable and Modular Architecture for CSS)由 Jonathan Snook 提出,将CSS分为五种类型的类别。

4.2 SMACSS的五种分类

分类英文说明前缀/命名规则
基础Base基础样式、重置样式无前缀,如 body
布局Layout页面布局框架l- 前缀
模块Module可复用的UI组件无前缀,如 .card
状态State元素的动态状态is- 或 has- 前缀
主题Theme可选的主题覆盖theme- 前缀

4.3 SMACSS 完整示例

/* ========== 1. 基础样式 ========== */
body {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: #333;
}

a {
  color: #007bff;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* ========== 2. 布局样式 ========== */
.l-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

.l-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 60px;
}

.l-sidebar {
  width: 250px;
  float: left;
}

.l-main {
  margin-left: 270px;
}

.l-footer {
  clear: both;
  padding: 40px 0;
}

/* ========== 3. 模块样式 ========== */
.card {
  padding: 20px;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  margin-bottom: 20px;
}

.card__title {
  font-size: 20px;
  font-weight: 600;
  margin-bottom: 12px;
}

.card__body {
  font-size: 14px;
  line-height: 1.6;
  color: #666;
}

.nav {
  display: flex;
  list-style: none;
  padding: 0;
  margin: 0;
}

.nav__item {
  padding: 8px 16px;
}

.nav__link {
  color: #333;
  font-size: 14px;
}

/* ========== 4. 状态样式 ========== */
.is-active {
  font-weight: 600;
  color: #007bff;
}

.is-expanded {
  display: block;
}

.is-hidden {
  display: none;
}

.is-disabled {
  opacity: 0.5;
  pointer-events: none;
}

.has-error {
  border-color: #dc3545;
}

/* ========== 5. 主题样式(可选) ========== */
.theme-dark {
  background-color: #1a1a1a;
  color: #e0e0e0;
}

.theme-dark .card {
  background-color: #2d2d2d;
  border-color: #444;
}

.theme-dark .card__title {
  color: #fff;
}

五、kebab-case 命名规范

5.1 什么是 kebab-case?

kebab-case(短横线命名法)是一种使用小写字母和连字符(-)的命名方式:

/* kebab-case 示例 */
.main-header { }
.nav-item { }
.user-profile-card { }
.btn-primary { }
.text-align-center { }

5.2 与其他命名风格对比

命名风格示例CSS中是否推荐说明
kebab-casemain-header强烈推荐CSS社区主流
camelCasemainHeader不推荐与JS命名冲突
PascalCaseMainHeader不推荐与组件名冲突
snake_casemain_header不推荐不符合CSS惯例
UPPER_CASEMAIN_HEADER不推荐与常量命名冲突

5.3 kebab-case 与 BEM 结合

/* kebab-case + BEM 的组合 */
.product-card { }                     /* Block */
.product-card__image-gallery { }      /* Element */
.product-card__add-to-cart { }        /* Element */
.product-card--on-sale { }            /* Modifier */
.product-card--out-of-stock { }       /* Modifier */

六、命名方法论对比

6.1 全面对比表

特性BEMOOCSSSMACSSkebab-case
核心思想组件化命名结构皮肤分离分类架构简洁命名风格
学习曲线中等较低中等低
适用范围组件化项目UI库/框架大型项目任何项目
命名长度较长中等中等短
样式冲突防护强中等中等弱
可维护性高高高中等
团队协作友好度高中等高中等
工具支持广泛有限有限广泛
与预处理器配合好好好好
推荐场景中大型组件化项目UI库和框架大型企业项目小型项目和原型

6.2 如何选择?

项目规模判断:

小型项目 / 原型
  └── 推荐:kebab-case + 简单的模块化命名

中型项目
  └── 推荐:BEM 或 SMACSS

大型企业项目
  └── 推荐:BEM + SMACSS 混合使用

UI 框架 / 组件库
  └── 推荐:OOCSS + BEM

SPA 单页应用
  └── 推荐:CSS Modules 或 CSS-in-JS

七、实用命名模式

7.1 状态命名模式

/* 使用 is-/has- 前缀命名状态 */
/* 状态 */
.is-loading { /* 加载中 */ }
.is-active { /* 激活状态 */ }
.is-expanded { /* 展开状态 */ }
.is-collapsed { /* 折叠状态 */ }
.is-visible { /* 可见 */ }
.is-hidden { /* 隐藏 */ }
.is-disabled { /* 禁用 */ }
.is-selected { /* 选中 */ }
.is-focused { /* 聚焦 */ }
.is-invalid { /* 无效 */ }
.is-valid { /* 有效 */ }

/* 拥有某特征 */
.has-dropdown { /* 有下拉菜单 */ }
.has-sidebar { /* 有侧边栏 */ }
.has-error { /* 有错误 */ }
.has-success { /* 成功 */ }
.has-warning { /* 有警告 */ }

7.2 JavaScript 钩子命名

/* 使用 js- 前缀标记仅供 JavaScript 使用的类 */
.js-modal { }          /* JavaScript 绑定用 */
.js-toggle-menu { }    /* 菜单切换触发器 */
.js-scroll-target { }  /* 滚动目标 */

/* 注意:js- 类不应该包含任何样式 */
/* 样式类和 JS 钩子类应该分开 */
<!-- 正确:样式类和JS钩子类分开 -->
<button class="btn btn--primary js-modal-trigger">
  打开弹窗
</button>

<!-- 不推荐:样式类和JS钩子混在一起无法区分 -->
<button class="open-modal-btn primary">
  打开弹窗
</button>

7.3 响应式命名模式

/* 使用前缀标记响应式断点 */
/* 移动端优先 */
.grid { display: block; }
.grid__item { width: 100%; }

/* 平板 */
@media (min-width: 768px) {
  .grid--md { display: flex; flex-wrap: wrap; }
  .grid__item--md-6 { width: 50%; }
  .grid__item--md-4 { width: 33.333%; }
  .grid__item--md-3 { width: 25%; }
}

/* 桌面端 */
@media (min-width: 1024px) {
  .grid--lg { display: flex; flex-wrap: wrap; }
  .grid__item--lg-6 { width: 50%; }
  .grid__item--lg-4 { width: 33.333%; }
  .grid__item--lg-3 { width: 25%; }
}

八、命名规范的团队实践

8.1 建立命名规范文档

## 团队CSS命名规范

### 基本原则
1. 使用 kebab-case 命名
2. 使用 BEM 命名结构
3. 状态使用 is-/has- 前缀
4. JS钩子使用 js- 前缀
5. 命名应该有语义,避免外观描述

### 禁止的命名
- 禁止使用缩写(mt10 → margin-top-10)
- 禁止使用颜色命名(red-btn → btn--danger)
- 禁止使用数字后缀(header1, header2)
- 禁止使用 !important(除非有充分的理由和注释)
- 禁止使用标签选择器覆盖组件样式

8.2 Stylelint 命名规则检查

{
  "rules": {
    "selector-class-pattern": [
      "^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z0-9]+(-[a-z0-9]+)*)?(--[a-z0-9]+(-[a-z0-9]+)*)?$",
      {
        "message": "类名必须使用 kebab-case 或 BEM 格式"
      }
    ]
  }
}

总结

CSS命名看似简单,实则是前端工程化的重要一环。选择合适的命名方法论,可以:

  1. BEM 适合组件化开发,命名清晰但较长
  2. OOCSS 适合构建UI库,强调复用性
  3. SMACSS 适合大型项目,提供架构指导
  4. kebab-case 是最基础的命名风格,适合所有项目

在实际项目中,这些方法论并不是互斥的。最常见的组合是 BEM + kebab-case*,再加上 *SMACSS 的分类思想来组织文件结构。关键是根据团队规模和项目需求,选择并坚持使用一套统一的规范。

记住:一致的命名比完美的命名更重要。

阅读 15