跨组件间距必须由父容器用外部修饰符控制,BEM组件本身不带外边距,所有间距逻辑仅限父级block的语义化修饰符(如_layout__area_margin-bottom-l),绑定CSS变量实现主题适配,禁止子组件写margin、修饰符叠加或混用gap与margin。

跨组件间距必须由父容器用外部修饰符控制
BEM 组件本身不带外边距,跨组件的间距不是子组件的事,而是父容器的布局职责。比如 .card 和 .button 相邻时,谁来决定它们之间的距离?答案是:包裹它们的父块,比如 .page-section 或 .form-layout,并通过类似 .page-section__content_card-above-button 这样的修饰符声明意图。
常见错误现象:.card 内部写了 margin-bottom: 1rem,.button 又加了 margin-top: 0.5rem —— 两者一叠加,视觉间距变成 1.5rem,且无法被父级统一收口;更糟的是,把 .button 换到模态框里,这段 margin-top 就成了多余空白。
- 所有跨组件间距逻辑,只允许出现在父级 block 的修饰符中,如
.layout__area_margin-bottom-l - 修饰符名必须体现布局语义(
_margin-bottom-l),而非数值(_mb-16)或组件名(_card-after-button) - CSS 实现必须绑定 CSS 变量:
.layout__area_margin-bottom-l { margin-bottom: var(--space-l); },确保主题、缩放、暗色模式下自动适配 - 禁止在子组件上写
margin-top去“对齐”另一个组件——这是反模式,会触发父子 margin 塌陷且破坏复用性
避免修饰符叠加导致间距失控
当多个外部修饰符同时作用于同一 block(如 .card_margin-top-s _margin-bottom-m),CSS 层叠会让 margin 值实际生效,而不是“取最大值”。结果可能是顶部留白 0.5rem + 底部留白 1rem,但设计系统本意是只保留底部留白。
这不是 BEM 的缺陷,而是 CSS 规则叠加的自然行为。关键在于约束修饰符的设计契约:
立即学习“前端免费学习笔记(深入)”;
- 每个修饰符只声明一个方向的 margin:
_margin-top-s只设margin-top,不碰margin-bottom等其他方向 - 禁止组合相反方向的修饰符(如
_margin-top-s _margin-top-m),可用 ESLint 插件stylelint-selector-bem-pattern或构建时校验拦截 - 对高频容器(如
.list、.grid)提供“无间距”基础修饰符:.list_compact主动重置所有外距为0,再由父级按需添加 - 若真需双向控制(如弹窗浮层需上下留空),改用独立语义修饰符:
.modal_framed,其 CSS 内部统一定义padding-block: var(--space-xl),而非拼凑 margin
Flex/Grid 容器内别混用 margin 和 gap
在 display: flex 或 display: grid 的父容器中,给子组件加 margin-right 或 margin-bottom 是危险操作——它和 gap 共存时行为不可预测,尤其在响应式断点切换或浏览器兼容 fallback 场景下。
典型问题:.button-group 设了 gap: 0.5rem,但内部 .button 自己又写了 margin-right: 0.25rem,结果间距忽大忽小,DevTools 里 margin 显示生效,gap 却被静默忽略。
- Flex/Grid 容器的子项,禁止写任何方向的
margin(除极少数装饰性偏移,如图标微调) - 间隔统一交给容器的
gap、row-gap、column-gap控制 - 老浏览器 fallback 方案必须退回到单侧 margin +
:not(:last-child),且确保父容器自身不设margin-top防止父子塌陷 - 若组件需在不同容器中复用(如既进 Flex 又进 Block 流),把它封装成“无外距”原子组件,间距责任完全外移
工具类命名必须带 u- 前缀且数值映射明确
项目里出现 mt-4 或 margin-top-2 这类工具类,基本等于埋雷——它没命名空间,无法区分是设计系统定义、第三方库注入,还是某人随手写的临时样式。BEM 工程中唯一可接受的工具类,是带 u- 前缀、数值严格映射设计 scale 的那一组。
例如 u-mt-4 必须对应 margin-top: var(--space-4),而 --space-4 得在 :root 或主题上下文中明确定义为 1rem(或其它约定值)。否则换主题、切字号、开缩放时,间距就脱节。
-
u-mt-1到u-mt-8覆盖 0.25rem 步长的 scale(0, 0.25rem, 0.5rem…),禁用u-mt-half这类非数字命名 - 工具类必须置于
@layer utilities中,确保层叠优先级高于组件样式 - BEM 组件内部(如
.card__body)禁止使用u-mt-2—— 外距只能由 block 层级修饰符控制 - JS 动态拼接类名时,必须用
cn('u-mt-', size),其中size是预定义的数字键(如4),不能传字符串'half'
_margin-bottom-16 看似直白,但一旦设计系统升级 spacing scale,所有这类类名就得全局替换;而 _margin-bottom-m 只要更新 --space-m 的值,全站自动同步。这不是命名偏好,是维护成本的分水岭。


















