block 是独立、语义清晰、可复用的 ui 单元(如 user-card、document-item),非泛义容器;element 仅限一级子级,命名不含 block 语义(card__title ✅,card__card-title ❌);modifier 表达稳定状态(btn--loading ✅,btn--red ❌),且不可单独使用。

Block 怎么定:组件即 Block,不是 DOM 容器
Block 不是随便一个 <div> 或父级 wrapper,而是有独立功能、语义清晰、可复用的 UI 单元。比如 <code>user-card、search-form、document-item 都是合格 Block;而 container、section-2、wrap 这类泛义词不能当 Block 名。
常见错误是把视觉分组当 Block:一个列表页里硬套 list-page__item,结果 item 本身在详情页也用——它早该是独立 Block document-item,而不是子元素。
- Vue/React 中,组件名小写中划线化就是天然 Block 名(
UserCard.vue→user-card) - Block 名必须全局唯一,避免
button和第三方库冲突,推荐加前缀如nc-button - 不要为“位置”命名:
sidebar__btn错,btn或btn--primary对
Element 怎么写:只允许一级,不嵌套不越界
__ 只能出现在 Block 和直接子部分之间,禁止 block__elem__sub 或 block__elem .sub 这类写法。Element 是逻辑归属,不是 DOM 深度。
比如按钮里的图标和文字,写成 btn__icon、btn__text 就够了;不要因为 HTML 里 <span><i></i></span> 就搞出 btn__text__icon —— 这违反 BEM 基本隔离原则。
- Element 名不含 Block 语义:
card__title✅,card__card-title❌ - Element 必须依附于且仅属于一个 Block,跨 Block 复用(如
btn在card里出现)应保持独立,用card__action表达组合关系,而非card__btn - SCSS 中只用单层嵌套:
.btn { &__icon {} &__text {} },禁用&__icon &__sub或空格后代选择器
Modifier 怎么用:状态驱动,不描述样式,不单独存在
-- 修饰符必须表达明确的状态或意图,比如 btn--loading、menu--horizontal、input--error;写成 btn--red 或 card--big 是反模式——颜色和尺寸会变,但“主按钮”“错误态”不会。
Modifier 永远不能脱离 Block 或 Element 单独使用:btn--primary 合理,btn__text--large 越界(字体大小是按钮整体的视觉变体,不是文字自身的状态)。
- Modifier 可叠加:
btn btn--primary btn--disabled是合法组合 - 避免布尔值或临时标签:
--v2、--new、--temp一律禁用 - 状态类(如
is-disabled)可作为补充,但不属于标准 BEM 三层,慎用,优先走--disabled
怎么落地不翻车:工具链和协作边界
纯手写 BEM 类名容易拼错双下划线或漏 Modifier,尤其在 JSX/Vue 模板里频繁拼接字符串时。靠人盯不如靠约束。
React 推荐用 clsx + Block 常量封装生成函数;Vue 3 的 setup 里可用 computed 返回 BEM 工厂方法。别用 PostCSS 插件自动生成类名——它绕过语义思考,改结构后类名就失联。
- 禁用 CSS-in-JS 动态插值:
${prefix}__text破坏可搜索性,审查元素看到的是乱码 - 老项目或 Ant Design/Element Plus 等第三方组件,不要强行重命名;用 BEM 写新模块,旧代码用工具类(如
mt-4)隔离,避免连锁崩坏 - 团队必须约定 Block 名唯一性校验方式,比如 ESLint + 自定义规则检查
class="xxx"是否符合^[a-z][a-z0-9-]*(__[a-z][a-z0-9-]*)?(--[a-z][a-z0-9-]*)?$
BEM 的复杂点不在语法,而在每次写类名前得判断:这到底是不是一个独立 Block?它有没有被错误地当成别人的孩子?Modifier 表达的是不是稳定意图?这些判断没法自动化,只能靠人盯住边界。











