← 博客 · · ConvertCaseHQ 团队 · 阅读约需 6 分钟

CSS 类名命名规范:kebab-case、BEM 及其他方案

大多数 CSS 类名都写成小写的 kebab-case(短横线命名),比如 .site-header、.price-tag,因为 CSS 给自己的东西命名时就是这么写的:background-color、font-size、flex-direction。BEM 在此基础上加了一套固定结构 block__element--modifier,所以像 card__title--large 这样的类名能告诉你三件事:它属于哪个组件、是组件的哪个部分、显示的是哪种变体。

下文会讲:CSS 中哪些地方区分大小写、哪些不区分,kebab-case 为什么成了默认选择,BEM 的命名规则究竟该怎么理解,以及什么时候换一种方案就用不着 BEM 了。

CSS 中哪些区分大小写,哪些不区分

CSS 本身的语法在 ASCII 范围内不区分大小写,所以 COLOR: RED 和 color: red 的效果完全一样。但有三样东西不同:

  • 类名区分大小写。.Card 匹配不到 class="card"。唯一的例外是处于怪异模式(quirks mode)的页面,浏览器会不区分大小写地匹配类名,这也是每个页面都应该以 <!DOCTYPE html> 开头的又一个理由。
  • 自定义属性区分大小写。--brand-color 和 --Brand-Color 是两个不同的变量。
  • HTML 元素名和属性名不区分。在 HTML 文档中,DIV 和 div 选中的是相同的元素;在 XML 中,二者就不同了。

想彻底不再操心这件事,最简单的办法就是让所有类名和自定义属性都保持小写。至于其他语言和文件系统如何处理大小写,请参阅我们的大小写敏感性对比。

为什么 CSS 类名最终选择了 kebab-case

CSS 并没有强制类名使用 kebab-case,.cardTitle 和 .card_title 都是合法的。用连字符之所以顺理成章,是因为类名周围的一切本来就在用它:属性名、space-between 这样的值,以及 aria-label 和 data-user-id 这样的 HTML 属性。在满是连字符的样式表里冒出一个 camelCase(驼峰命名)类名,看起来就像打错了字。

kebab-case 在 JavaScript 里会有些别扭,因为标识符中不允许出现连字符。浏览器会处理好它自己的那部分:data-user-id 读作 element.dataset.userId,background-color 变成 element.style.backgroundColor。类名通常仍以字符串形式出现(classList.add("is-open")),所以不受影响。

例外是 CSS Modules。CSS Module 会把类名作为一个对象导出给 JavaScript,所以 .card-title 必须写成 styles["card-title"],而 .cardTitle 则可以写成更整洁的 styles.cardTitle。这是唯一一种我们会把类名改成 camelCase 的场景。

BEM 命名:块、元素、修饰符

BEM(Block、Element、Modifier,即块、元素、修饰符)出自 Yandex。这套方法论的历史页面把相关工作追溯到 2005 年,把 Yandex 于 2009 年 3 月发布 Lego 2.0 框架视为 BEM 的开端,并把开源的时间定在 2010 年。它的命名规则很简短:

  • 块(Block):一个独立的组件,单独拿出来也有意义,比如 .card、.search-form 或 .menu。
  • 元素(Element):块的一部分,离开块就没有意义,命名方式是块名、两个下划线、元素名:.card__title、.menu__item。
  • 修饰符(Modifier):改变外观、行为或状态的标记,命名方式是块名或元素名、两个连字符、修饰符名:.card--featured、.card__button--disabled。修饰符也可以是一个键加一个值:.form--theme-xmas。

每个部分内部的单词用单个连字符分隔,例如 .search-form__submit-button。下面是一个完整的组件:

<article class="card card--featured">
  <h2 class="card__title">Spring sale</h2>
  <p class="card__body">Everything 20% off this week.</p>
  <a class="card__button card__button--disabled" href="/sale">Shop now</a>
</article>
.card { border: 1px solid #ddd; padding: 1rem; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; }
.card__button--disabled { opacity: 0.5; pointer-events: none; }

经典 BEM 与双连字符风格

如果你去看这套方法论的官方网站 bem.info,会发现修饰符的写法不一样。它的经典方案用单个下划线分隔修饰符:menu_hidden、menu__item_visible、menu__item_type_radio。-- 写法在那里被列为一种替代方案,即“Two Dashes style”(双连字符风格),getbem.com 介绍的也是这种写法。两种都是正统的 BEM,只是不要在同一个代码库里混用。bem.info 还提到双连字符有个小问题:HTML 注释里出现双连字符,可能无法通过验证。

容易用错的 BEM 规则

  1. 不要出现元素的元素。卡片头部里的标题仍然是 card__title,而不是 card__header__title。在 HTML 中,元素想嵌套多深都可以,名称却始终保持扁平。bem.info 给出的理由是:如果名称层层串联,你就无法在不改动现有代码的情况下移动、添加或删除元素。
  2. 修饰符从不单独使用。要写 class="button button--primary",而不是 class="button--primary"。修饰符只是对基础类的补充。
  3. 只用单个类选择器。写 .card__title,而不是 .card .card__title 或 h2.card__title。公认的例外是会改变其内部元素的块修饰符:.card--featured .card__title。
  4. 按用途给修饰符命名。这一条是我们的看法,并非 BEM 规则:--featured 或 --error 经得起改版,而 --yellow 在品牌色更换的那天就名不副实了。
  5. 别把 JavaScript 钩子混进样式类。这也是我们的看法:在脚本中用 data-action="close" 这样的 data 属性来选择元素,而不是用 .card__button,这样出于样式原因重命名类时,就不会破坏功能。

以后要重命名某个块,基本上就是查找和替换。把模板粘贴到我们的查找与替换工具中,勾选“正则”,把 card(__|--) 替换为 product-card$1:所有元素类和修饰符类都会换成新的块名,匹配数会告诉你改了多少处。不带后缀的 card 类要单独重命名,以免误改无关的单词。

用 Sass 和原生 CSS 嵌套编写 BEM

Sass 的父选择器可以给外层选择器加后缀,它的文档用的例子正是 BEM。原生 CSS 嵌套做不到这一点,MDN 也明确指出了这一点:嵌套的 &__title 会被当作类型选择器,所以不会生成 .card__title。

// Sass: works
.card {
  &__title { font-size: 1.25rem; }    // compiles to .card__title
  &--featured { border-color: gold; } // compiles to .card--featured
}
/* Native CSS nesting: write the full class names */
.card {
  border: 1px solid #ddd;
  &.card--featured { border-color: gold; } /* .card.card--featured */
}
.card__title { font-size: 1.25rem; }

BEM 之外:什么时候不需要它

BEM 的存在,是为了防止类名冲突,并在全局样式表中让结构一目了然。如果别的方式已经解决了冲突问题,BEM 那些长名称带来的好处就少了:

  • CSS Modules 默认把类名限定在局部作用域内,所以一个组件里的普通 .title 不会和另一个组件里的 .title 冲突。
  • Utility-first CSS(工具类优先的 CSS),比如 Tailwind,基本上用 font-bold 这类预定义的工具类取代了自定义类名,于是命名工作转移到了组件里。
  • 小型网站只有少数几个组件,通常用普通的 kebab-case 再给每个组件加个前缀就够了。

即便如此,凡是你确实要自己写的类名,kebab-case 仍然是稳妥的默认选择。

经验法则

  • 类名和自定义属性一律使用小写 kebab-case。
  • 多人共用一份全局样式表时用 BEM:block__element--modifier,元素名保持扁平,修饰符与基础类并列。
  • 只有当 CSS Modules 这类工具把类名暴露给 JavaScript 时,才使用 camelCase 类名。

要把组件名变成类名?在我们的大小写转换器中输入“Product Card”,点击 kebab-case 按钮就能得到 product-card。至于项目中其他各层的命名规范,从环境变量到 URL,请参阅我们的开发者命名风格速查表。

© 2026 ConvertCaseHQ. 保留所有权利。