模块边界由功能独立性和复用意图决定,非文件夹划分;.user-profile是模块因其可跨上下文复用,.section-2不是因其仅为视觉分组;Block须能脱离父容器直接复用,Element禁止回溯父级语义,Modifier须语义化可枚举,严禁跨模块混用或超一级嵌套。

模块边界不清晰,类名就容易越界
模块不是靠文件夹划分的,而是靠功能独立性和复用意图决定的。一个 .user-profile 是模块,因为它能被用户列表、详情页、弹窗同时引用;而 .section-2 不是模块,它只是视觉分组,没有明确职责,也不具备跨上下文复用能力。
常见错误现象:.header__nav 被复制粘贴到 footer 里改写成 .footer__nav,结果两个 nav 的样式逻辑实际一致——说明 nav 本该是独立 Block,而不是 header 的子元素。
- 判断 Block 是否成立,只问一个问题:它能否脱离当前父容器,在其他页面/组件中直接复用?能 → 单独定义为 Block
- Element 名称禁止回溯父级语义,
.card__card-title错误,.card__title正确 - 避免用
layout、container、wrapper这类泛化词命名 Block,它们无法表达具体功能
Block 名就是天然命名空间,别画蛇添足加前缀
项目里不需要 .myapp-button 或 .ui-button。BEM 的设计前提是:Block 名本身已承担命名空间职责。.button 就代表“我们项目自己的按钮”,不是第三方库也不是浏览器默认样式。
加前缀反而带来实际问题:编辑器补全变慢、Git diff 噪声增多、CSS-in-JS 中字符串拼接更易出错。除非你真在做跨项目共享的 UI 包(如设计系统 npm 包),否则前缀纯属冗余。
立即学习“前端免费学习笔记(深入)”;
- 团队协作时,统一 Block 命名比统一前缀更重要。比如都用
.search-form,而不是有人写.search、有人写.search-bar - 如果已有历史代码混用
.btn和.button,优先收敛为语义明确的.button,而不是新增.legacy-btn这类临时方案
元素和修饰符必须严格绑定 Block,不能跨模块混用
.button__icon 只能属于 .button 模块;如果你在 .card 里也放了个 icon,且行为/样式与 button 中的 icon 不同,那就该定义 .card__icon,而不是复用 .button__icon —— 否则等于把两个模块的样式耦合在一起。
常见错误现象:.menu__item--active 被复制到 .tabs 模块里改成 .tabs__item--active,但 active 的交互逻辑(如键盘焦点、ARIA 属性)其实完全一致——这说明 --active 是通用状态,应由上层组件或工具函数控制,而非靠类名硬编码。
- Modifier 值必须可枚举、语义化:
.button--primary✅,.button--red❌ - Element 不得嵌套:禁止
.list__item__icon,遇到需要两层结构时,优先拆出新 Block,如.icon或.status-badge - SCSS 中只允许单层嵌套:
.button { &__icon {} &--primary {} },禁止&__icon { &__svg {} }
React/Vue 组件中怎么守住 BEM 边界
最常破防的地方是 JSX 里手动拼字符串:className={`button ${props.disabled ? 'button--disabled' : ''}`。问题不在语法,而在于 Modifier 状态散落在多处,后续加 hover/focus/loading 等状态就变成一长串三元表达式。
更危险的是 CSS Modules 下写 className={styles['button__icon']} —— 这等于绕过组件对 Block 边界的封装,一旦 button__icon 抽成独立 .icon Block,所有硬编码的地方都会失效。
- 每个组件顶部定义常量:
const BLOCK = 'user-profile',所有类名基于它生成:${BLOCK}__avatar - 用
clsx或classnames收敛状态逻辑,但不要把 Block 名写死在工具调用里,而是作为参数传入 - 动态生成类名时,确保
__和--的拼接位置绝对固定,避免出现button__icon--disabled写成button__icon-disabled(少一个-)这类低级错误


















