首页
/ Material UI CSS Cascade Layers:用 @layer 生成并控制 Material UI 样式的单层与多层模式

Material UI CSS Cascade Layers:用 @layer 生成并控制 Material UI 样式的单层与多层模式

2026-09-06 23:31:11作者:乔或婵

本文基于 Material UI 官方文档 css-layers.md 展开,讲解如何用 CSS Cascade Layers(级联层)生成 Material UI 样式:先启用单一的 @layer mui 层,再通过主题的 modularCssLayers 选项将样式拆分为 mui.globalmui.componentsmui.thememui.custommui.sx 五个层级。读完后你将掌握在 Next.js(App Router 与 Pages Router)、Vite 等 SPA 框架中开启级联层的完整配置,理解每类样式被分配到哪个层的源码机制,并避开启用多层级联后可能出现的优先级变化坑。

什么是级联层,Material UI 为什么支持它

级联层(Cascade Layers)是一项进阶 CSS 特性,它让你显式控制样式规则应用到元素上的先后顺序,而不必依赖“谁写在后面、谁选择器权重高”的隐式规则。Material UI 对级联层的集成带来三类实际收益(出自官方文档):

  • 可预测的优先级:你可以控制样式的顺序,从而避免 specificity(特异性)冲突。例如,主题化某个组件时,不需要去硬拼默认样式的权重,主题覆盖就能按层顺序生效。
  • 与 CSS 框架更好的集成:有了级联层,你可以用 Tailwind CSS v4 的工具类直接覆盖 Material UI 样式,而不再需要 !important 指令。
  • 更好的可调试性:级联层会显示在浏览器开发者工具中,能直观看到哪些样式被应用、以什么顺序被应用。

Material UI 的集成分两级:单一级联层(所有样式包进一个 @layer mui)与多个级联层(按样式来源拆成五个子层)。下面分别给出各框架的配置方式。

实现单一级联层:@layer mui

单级联层模式为所有 Material UI 组件与全局样式创建一个名为 @layer mui 的层。它适合与 Tailwind CSS v4 等同样使用 @layer 指令的其他样式方案集成。

Next.js App Router

先在 Next.js 中按官方 App Router 集成指南配置好 Material UI,然后做两步:

  1. 在根布局中启用 CSS layer 功能:
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';

export default function RootLayout() {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          {/* Your app */}
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}
  1. 在 CSS 文件顶部配置层顺序,以便与 Tailwind CSS v4 协作:
@layer theme, base, mui, components, utilities;

Next.js Pages Router

在自定义 _document 中启用 CSS layer 功能:

import {
  createCache,
  documentGetInitialProps,
} from '@mui/material-nextjs/v15-pagesRouter';

// ...

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const finalProps = await documentGetInitialProps(ctx, {
    emotionCache: createCache({ enableCssLayer: true }),
  });
  return finalProps;
};

然后用 GlobalStyles 组件配置层顺序以便与 Tailwind CSS v4 协作——注意它必须是 AppCacheProvider 的第一个子元素:

import { AppCacheProvider } from '@mui/material-nextjs/v15-pagesRouter';
import GlobalStyles from '@mui/material/GlobalStyles';

export default function MyApp(props: AppProps) {
  const { Component, pageProps } = props;
  return (
    <AppCacheProvider {...props}>
      <GlobalStyles styles="@layer theme, base, mui, components, utilities;" />
      <Component {...pageProps} />
    </AppCacheProvider>
  );
}

createCache({ enableCssLayer: true }) 是 Material UI 为 Next.js 封装的 Emotion cache 创建函数,仓库中对应实现位于 createCache.ts,它在构建 Emotion cache 时透传 enableCssLayer 选项。

Vite 或其他 SPA

src/main.tsx 中做两处修改:向 StyledEngineProvider 传入 enableCssLayer,并用 GlobalStyles 配置层顺序:

import { StyledEngineProvider } from '@mui/material/styles';
import GlobalStyles from '@mui/material/GlobalStyles';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <StyledEngineProvider enableCssLayer>
      <GlobalStyles styles="@layer theme, base, mui, components, utilities;" />
      {/* Your app */}
    </StyledEngineProvider>
  </React.StrictMode>,
);

源码视角:单层的 @layer mui 是如何注入的

enableCssLayer 的核心实现在 StyledEngineProvider.tsxgetCache(injectFirst, enableCssLayer) 做了三件关键的事:

  1. 独立缓存 key:启用级联层时,Emotion cache 的 key 从默认的 css 改为 mui(第 98 行 key: enableCssLayer ? 'mui' : 'css')。源码注释解释了原因:若未分层与已分层的两条规则碰巧哈希出相同的类名,未分层规则会因“层外样式优先于层内样式”而击败 Material UI 自己的样式,独立 key 可以从命名上杜绝这种碰撞。
  2. 自动包裹 @layer muiStyledEngineProvider 重写了 cache 的 insert 方法(第 103–112 行),每条插入的样式如果不是顶层的 @layer 声明,都会被包成 @layer mui { ... };正则 /^@layer\s+[^{]*$/ 专门放行层顺序声明,避免嵌套 @layer
  3. 全局样式插入位置:通过一个自定义 MyStyleSheet 子类(第 80–90 行),让 key 以 global 结尾的样式表插到插入点之前,保证 GlobalStyles(包括层顺序声明)始终位于其他 Emotion 样式之前。

这也解释了为什么在 Pages Router 中要求 GlobalStylesAppCacheProvider 的第一个子元素:层顺序声明必须先于任何被分层的样式出现,浏览器才会按声明的顺序解析各层。

实现多个级联层:modularCssLayers

在启用单级联层之后,可以用主题选项 modularCssLayers 把样式进一步拆分为多个层,以便更好地组织 Material UI 内部样式,也让主题化与 sx 覆盖更容易控制。

做法分三步:先按上一节为所用框架启用 CSS layer 功能;然后新建一个文件,导出一个包裹 Material UI ThemeProvider 的组件;最后向 createTheme 传入 modularCssLayers: true

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  modularCssLayers: true,
});

export default function AppTheme({ children }: { children: ReactNode }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

官方示例 CssLayersInput.tsx 展示了一个在 modularCssLayers: true(同时开启 cssVariables: true)主题下渲染 FormControl/OutlinedInput 并用 sx 覆盖样式的完整用例。

启用该选项后,Material UI 生成这五个层:

层名 内容
@layer mui.global GlobalStylesCssBaseline 的全局样式
@layer mui.components 所有 Material UI 组件的基础样式
@layer mui.theme 所有 Material UI 组件的主题样式
@layer mui.custom 非 Material UI 的 styled 组件的自定义样式
@layer mui.sx sx prop 生成的样式

Next.js App Router 的多层配置

'use client';
import React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  modularCssLayers: true,
});

export default function AppTheme({ children }: { children: React.ReactNode }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
import AppTheme from '../theme';

export default function RootLayout() {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          <AppTheme>{/* Your app */}</AppTheme>
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

Next.js Pages Router 的多层配置

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  modularCssLayers: true,
});

export default function AppTheme({ children }: { children: ReactNode }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
import AppTheme from '../src/theme';

export default function MyApp(props: AppProps) {
  const { Component, pageProps } = props;
  return (
    <AppCacheProvider {...props}>
      <AppTheme>
        <Component {...pageProps} />
      </AppTheme>
    </AppCacheProvider>
  );
}
import {
  createCache,
  documentGetInitialProps,
} from '@mui/material-nextjs/v15-pagesRouter';

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const finalProps = await documentGetInitialProps(ctx, {
    emotionCache: createCache({ enableCssLayer: true }),
  });
  return finalProps;
};

Vite 或其他 SPA 的多层配置

import { createTheme, ThemeProvider } from '@mui/material/styles';

const theme = createTheme({
  modularCssLayers: true,
});

export default function AppTheme({ children }: { children: ReactNode }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
import AppTheme from './theme';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <StyledEngineProvider enableCssLayer>
      <AppTheme>{/* Your app */}</AppTheme>
    </StyledEngineProvider>
  </React.StrictMode>,
);

与其他样式方案协作:用字符串指定完整层顺序

要集成 Tailwind CSS v4 等其他样式方案时,把 modularCssLayers 的布尔值替换为一段指定层顺序的字符串。Material UI 会查找其中的 mui 标识符,并把五个子层按正确顺序展开进去:

 const theme = createTheme({
-  modularCssLayers: true,
+  modularCssLayers: '@layer theme, base, mui, components, utilities;',
 });

最终生成的 CSS 是:

@layer theme, base, mui.global, mui.components, mui.theme, mui.custom, mui.sx, components, utilities;

这段展开逻辑在 useLayerOrder.tsx 中:默认层顺序为 mui.global, mui.components, mui.theme, mui.custom, mui.sx;若传入字符串,则用正则 /mui(?!\.)/g 只替换裸的 mui 标识符((?!\.) 负向前瞻保证不会误伤 mui.unknown 这类子层名)。对应的测试 useLayerOrder.test.tsx 覆盖了四种场景:布尔模式注入默认顺序、字符串模式展开子层、含点号的 mui.unknown 不被替换、嵌套主题(存在上层 ThemeProvider)时跳过注入以避免重复元素。

该 hook 还负责客户端注入:从源码注释可见,它在服务端以 GlobalStyles 形式设置层顺序,在客户端则通过 useEnhancedEffect 把层顺序声明以带 data-mui-layer-order 属性的 <style> 元素插入 <head> 最前面(第 26–50 行),确保层顺序永远先于其他 Emotion 样式存在;已有相同 id 的元素时不会重复插入。

源码视角:五种样式分别如何被分进各自的层

各层的内容分配发生在 styled 组件的样式处理链路中,核心在 createStyled.js

  1. components 与 custom 层layerName 的判定在第 149–152 行——组件名以 Mui 开头或属于组件插槽(componentSlot)时归入 components 层,否则归入 custom 层。也就是说,styled(Button)((...) => ...) 会落在 mui.components,而你自己 styled('div') 出的组件落在 mui.custom。只有当 props.theme.modularCssLayers 为真时才会真正包上 @layer(见第 195、202–210 行),shallowLayer(第 24–32 行)负责把序列化后的样式包裹成 @layer xxx { ... } 的形式。
  2. theme 层:主题的 styleOverrides(第 225–247 行)与 variants(第 249–263 行)在处理时统一传入 'theme' 作为 layerName,因此主题覆盖样式整体落在 mui.theme 层。
  3. sx 层sx prop 的样式由 styleFunctionSx.js 处理,同样依据 modularCssLayers 决定是否包裹进 mui.sx 层。
  4. global 层GlobalStyles 组件(GlobalStyles.tsx)与 CssBaseline 的样式归入 mui.global

这条链路也解释了多层模式的价值:组件基础样式、主题覆盖、sx 各自成层后,覆盖关系由层声明顺序决定,不再依赖选择器权重,sxmui.sx 层声明在最后)天然可以覆盖主题与组件默认样式。

注意事项:启用 modularCssLayers 可能改变现有外观

官方文档特别提示:如果在一个已经应用了自定义样式和主题覆盖的应用中启用 modularCssLayers,由于启用前后 specificity 计算方式不同,你可能观察到 UI 外观的意外变化。文档给出的例子是 Accordion 组件:

const theme = createTheme({
  components: {
    MuiAccordion: {
      styleOverrides: {
        root: {
          margin: 0,
        },
      },
    },
  },
});

默认情况下,当 Accordion 处于展开状态时,主题的 margin 覆盖不会胜过默认 margin 样式——因为展开态的默认样式权重更高,这段代码没有效果。

而启用 modularCssLayers 后,主题 margin生效:因为 mui.theme 层在级联顺序中位于 mui.components 层之后,层顺序压过了权重差异,样式覆盖被应用,展开的 Accordion 不再有 margin。这意味着启用多层级联层是一次“优先级语义变更”,迁移已有项目时建议先在关键组件上回归检查视觉表现。

小结与相关文件

  • 单层模式只需一个开关(enableCssLayer),把全部 Material UI 样式包进 @layer mui,适合与 Tailwind CSS v4 等工具链协作;多层模式在单层基础上再加 modularCssLayers,按样式来源细分为 mui.global / mui.components / mui.theme / mui.custom / mui.sx 五个层,并支持以字符串形式融入第三方层顺序。
  • 关键源码入口:单层包裹逻辑见 StyledEngineProvider.tsx,层顺序注入见 useLayerOrder.tsx,分层分配逻辑见 createStyled.js
  • 官方文档与示例:css-layers.mdCssLayersInput.tsxCssLayersCaveat.tsx
登录后查看全文
热门项目推荐
相关项目推荐