z-index冲突需通过容器级stacking context隔离解决:主应用为子应用容器设position:relative+z-index唯一值,子应用内弹窗统一用z-index:inherit或小固定值,禁用硬编码高z-index,UI框架z-index变量须构建时重写,挂载点必须指定容器节点而非document.body。

z-index 冲突是微前端里最隐蔽的样式污染
子应用弹窗、下拉菜单、Tooltip 的层级突然被主应用遮住,或者反过来盖住主应用导航栏——这不是 bug,是 z-index 在全局 CSS 作用域里天然失效。浏览器只认数值大小,不认“这是哪个子应用的弹窗”。哪怕你给子应用加了 .subapp-order 前缀,.subapp-order .el-dialog 的 z-index 仍和主应用的 .main-header 在同一竞争层。
别靠 CSS 前缀解决 z-index,得靠容器级 stacking context 隔离
单纯给类名加前缀(如 .subapp-order .el-drawer)对 z-index 无效,因为前缀不改变元素在 stacking context 中的位置。真正起效的是让每个子应用容器自身成为一个独立 stacking context:
- 主应用中为每个子应用挂载节点显式设置
position: relative+z-index: 1(或更高唯一值),比如<div id="subapp-order" style="position: relative; z-index: 100"></div> - 子应用内部所有浮动/弹出类(
.el-dialog、.ant-modal、.popover)统一用z-index: inherit或设为固定小值(如z-index: 10),依赖父容器的 stacking context - 禁用子应用内硬编码高 z-index(如
z-index: 9999),这类写法会穿透容器边界,直接参与全局层叠排序
UI 框架的 z-index 变量必须编译期注入,不能 runtime 覆盖
Element Plus 的 $--z-index-dialog、Ant Design Vue 的 @zindex-modal 这类 Sass 变量,如果只是在子应用入口 JS 里调用 app.use(ElementPlus, { zIndex: 1000 }),根本不会影响已编译的 CSS 文件里的 .el-dialog 规则——那些规则早在构建时就固化成 z-index: 2000 了。
- Vite 子应用必须在
src/styles/element-variables.scss中重写变量:$--z-index-dialog: 10;,并确保该文件被main.ts显式 import - Webpack 子应用需配置
css-loader的modules.localIdentName和sass-loader的additionalData注入命名空间变量 - 第三方 CSS(如
node_modules/element-plus/theme-chalk/index.css)必须走 PostCSS 重写:用postcss-zindex插件批量降级其 z-index 值,并确保它被包裹进子应用容器前缀选择器下
动态挂载时必须重置 document.body 的 stacking context
很多子应用(尤其 React)默认渲染到 document.body,一旦它插入一个 position: fixed 弹窗,就脱离了主应用容器的 stacking context,直接按 body 层叠——这时无论你怎么设容器 z-index 都没用。
立即学习“前端免费学习笔记(深入)”;
- 子应用初始化时强制指定挂载点为容器节点:
createRoot(containerEl).render(<app></app>),而非createRoot(document.body) - Vue 应用需在
mount前检查app.mount的 target 是否为传入的容器,禁用app.config.globalProperties.$message.open({appendToBody: true})类选项 - 主应用要防止自身样式意外创建新 stacking context:避免对
body或#app设置transform、opacity 、<code>will-change等触发属性
z-index: 2147483647 就能让整个隔离策略失效。


















