应使用 dialog 作 block 名,因其语义中性、复用边界清晰;dialog__overlay 必须是 element,dialog--visible 必须加在根节点上,以确保状态统一、动画连贯、结构正确。

直接叫 modal 作为 Block 名是错的,大概率和 Ant Design 的 ant-modal、Element Plus 的 el-modal 或项目里已有的 notification-modal 冲突;正确做法是用 dialog 作 Block 名,dialog__overlay 必须是 Element,且 dialog--visible 必须加在根节点上。
为什么不能用 modal 作 Block 名
Block 名不是随便起的容器标签,它得语义中性、复用边界清晰。modal 暗示“模态交互”,但实际场景中 confirm、toast、drawer 都可能带遮罩——它们不是 modal,却共享 dialog 的对话式容器语义。一旦你写了 modal__content,而项目里已有 ant-modal,CSS 选择器权重打架、JS 类名切换冲突、工具链(如 postcss-bem-linter)报错都是必然结果。
常见错误包括:
-
confirm-modal或modal-dialog:违反 BEM “Block 名必须原子化” 原则,前缀/后缀嵌套让 JS 切换类名时难维护 -
modal__overlay却没定义modal根类:HTML 里只有<div class="modal__overlay">,BEM 结构直接塌方 <li>用 <code>popup或overlay当 Block:和 tooltip、dropdown 的层叠逻辑混在一起,z-index无法收敛 - JS 得同时操作
dialog--visible和overlay--visible,稍有延迟就出现遮罩已显、内容未动的视觉断裂 - CSS 动画分散:
dialog__overlay靠opacity过渡,dialog__content靠transform位移,无法用同一个修饰符统一触发 - DOM 结构失效:
dialog__overlay必须和dialog__content在 HTML 中平级,且都挂载在dialog下,否则.dialog--visible .dialog__overlay选择器根本捕获不到
dialog__overlay 必须是 Element,不能独立为 Block
遮罩层不是独立功能单元:它不承载内容、不定义关闭逻辑、不单独复用,只依附于 dialog 存在。所以它只能是 dialog__overlay,不能是 overlay 或 dialog-overlay(单横线会被解析为新 Block)。
若拆成独立 Block,会立刻暴露三个硬伤:
正确结构只有一种:
<div class="dialog"> <div class="dialog__overlay"></div> <div class="dialog__content"></div> </div>
dialog--visible 必须加在根节点上
--visible 表达的是整个组件的状态,不是某个子元素的局部表现。React 场景下,setIsOpen(true) 只需给最外层 <div class="dialog"> 加 <code>dialog--visible,其余靠 CSS 选择器联动:
.dialog--visible .dialog__overlay {
opacity: 1;
visibility: visible;
}
.dialog--visible .dialog__content {
transform: translateY(0);
}
如果加在 dialog__overlay 上 → 遮罩显示,但内容没动,视觉断裂;如果用 dialog__overlay--visible 单独控制 → 状态粒度太细,违背 Modifier 只修饰自身变体的原则。
注意:transition 必须写在基础类(如 .dialog__overlay)里,而不是 .dialog--visible 里,否则隐藏时会跳变——这是最容易被忽略的动画陷阱。
尺寸与位置修饰符只能挂载在 dialog 本身
尺寸(--small/--large)、位置(--center/--top-left)是整个模态框的状态,不是某个子元素的视觉特征。它们必须作用于 dialog,由 Block 统一控制布局上下文。
写成 dialog__body--large 是错的:body 的尺寸由 dialog 容器控制,单独放大 body 会导致 padding 错位、按钮溢出等布局断裂。
实操建议:
- 禁止
dialog--size-large:修饰符值必须原子化、可枚举,--large比--size-large更短、更易读、更易被 JS 切换 - 禁止
dialog--mobile:响应式断点不是固有状态,应配合@media控制,而非塞进修饰符 - 所有遮罩复用同一套样式规则,仅通过命名区分归属:
drawer__overlay、toast__overlay,避免重复声明
真正容易被忽略的,是 dialog__overlay 必须用 position: fixed 硬写死,不能依赖父级定位——只要 dialog 上用了 transform 或 opacity ,就会创建新层叠上下文,子元素的 <code>z-index 就永远出不去。











