CSS命名规范BEM详解_Block-Element-Modifier的结构化设计

2次阅读

bem是一种css命名方法论,通过block-element-modifier三段式命名(如.menu、.menu__item、.menu–horizontal)解决样式作用域冲突问题,避免同名类名相互影响。

CSS命名规范BEM详解_Block-Element-Modifier的结构化设计

什么是BEM,为什么直接写 .header-nav 不行 BEM强制用三段式命名:Block(模块)、Element(元素)、Modifier(状态),比如 .menu.menu__item.menu--horizontal。不是为了好看,是为了解决CSS作用域失控——你改了 .nav,结果首页轮播图的“导航”也塌了,因为两个 .nav 碰巧同名又没隔离。 常见错误是把语义当结构,比如写 .user-card__name,但实际这个 name 在用户列表页也复用,它本质是独立 Block;或者把 Modifier 当成样式开关,写成 .button--red,结果设计师说“红色只是品牌色,换主题就得全搜替换”,其实该用 .button--primary 这种语义化修饰符。

实操建议:

立即学习前端免费学习笔记(深入)”;

  • Block 必须有独立功能和复用价值,不能只是视觉分组(比如 .section-2 就不合格)
  • Element 名称不带父级语义,.card__title 正确,.card__card-title 是冗余
  • Modifier 值必须可预测,避免 --v2--new 这类临时标签

嵌套层级怎么控制,scss 里能写多深 BEM本身禁止CSS嵌套超过一层,即只允许 .block__element.block--modifier,不允许 .block__element__subitem.block__element .subitem。这不是教条,是因为每多一层选择器,就多一分耦合风险:你改 .listdisplay,可能意外影响 .list__item__icon 的定位逻辑。 SCSS 里用 &__&-- 是安全的,但一旦出现 &__item &__icon(中间带空格),就等于生成了后代选择器,破坏BEM隔离性。

实操建议:

立即学习前端免费学习笔记(深入)”;

  • SCSS 中只用单层嵌套:.block { &__element {} &--modifier {} }
  • 遇到“子元素的子元素”,优先拆成新 Block,比如 .table 里的 .table-cell__badge 应改为 .badge(独立 Block)+ .badge--in-table
  • 如果真需两层视觉关系,用属性选择器替代,比如 [data-badge-type="status"],比 .cell__content__badge 更可控

React 组件里怎么用 BEM,classnames 怎么配 React 中最常踩的坑是把 BEM 当字符串拼接:写 className={`card__header ${isExpanded ? 'card__header--expanded' : ''}`。问题不在语法,而在于 Modifier 状态分散在 JSX 多处,后续加个 disabled 状态就得再补一串三元。 classnames 库能收敛逻辑,但要注意它不解决 Block 命名一致性问题——你传入 card__header,组件内部却用 header 作 Block 名,最终生成的类名对不上。

实操建议:

立即学习前端免费学习笔记(深入)”;

  • 每个组件顶部定义 Block 名,如 const BLOCK = 'user-profile',所有类名基于它派生
  • Modifier 用对象写法:classnames(`${BLOCK}__avatar`, { [`${BLOCK}__avatar--large`]: isLarge })
  • 避免在子组件里硬编码父级 Block 名,用 props 透传 Block 前缀,或用 useBem 自定义 Hook 统一管理

为什么 .btn--primary.btn-primary 更可靠 破折号(--)是 BEM 的语法锚点,它让类名具备机器可解析性。工具链能靠它区分 Block、Element、Modifier:postcss 插件可以自动校验 .menu__item--disabled 是否存在对应 .menu__item;VS Code 插件能高亮跳转到定义处;甚至 CI 脚本可以扫描出所有孤立的 Modifier 类名(写了 --hover 却没定义 __trigger)。 下划线风格(.btn_primary)或连字符风格(.btn-primary)会模糊边界:.btn-primary 看起来像 Block,但其实是 Modifier;.form-input-Error 分不清是 form__input-error 还是 form-input__error

实操建议:

立即学习前端免费学习笔记(深入)”;

  • 全项目统一用双破折号 -- 表示 Modifier,双下划线 __ 表示 Element,这是 BEM 解析器唯一认的标准
  • 别为了“更短”改成单破折号,.btn-primarywebpack + CSS Modules 下容易和本地作用域冲突
  • 如果团队已用 Tailwind,BEM 可退守为 Block 层命名规范(只约束 .card.modal 这些顶层类),Element/Modifier 交给 utility classes 承担

BEM 的复杂点从来不在语法,而在 Block 边界的判断——同一个 ui 区块,在列表页是 Element,在详情页可能就是独立 Block。这没法靠规则穷举,得靠每次写 __ 前问一句:它未来会被抽出来单独复用吗?没人问,就容易变成又一套“看起来规范”的技术债。

text=ZqhQzanResources