Skip to content

Conversation

@siriwatknp
Copy link
Member

@siriwatknp siriwatknp commented Apr 25, 2025

closes #33012
closes #28911
closes #30536

Docs: https://deploy-preview-46001--material-ui.netlify.app/material-ui/customization/css-layers/
Playground: https://stackblitz.com/edit/6a5ghxfh?file=src%2Findex.tsx

Motivation

By splitting styles into different CSS layers. It eliminates the specificity issue when theming or with sx prop.

For example, customize the padding of the DialogContent using sx prop does not work because the styles of the sx prop cannot beat the default styles from Dialog due to this selector .MuiDialogTitle-root+.css-1foygqf-MuiDialogContent-root:

<Dialog
  open={open}
  onClose={handleClose}
  aria-labelledby="alert-dialog-title"
  aria-describedby="alert-dialog-description"
>
  <DialogTitle id="alert-dialog-title">
    {"Use Google's location service?"}
  </DialogTitle>
  <DialogContent sx={{ pt: 2 }}> <<<<<<<< 
    <DialogContentText id="alert-dialog-description">
      Let Google help apps determine location. This means sending anonymous
      location data to Google, even when no apps are running.
    </DialogContentText>
  </DialogContent>
</Dialog>

User will need to increase the specificity in order to override. So if styles are split into different CSS layers @layer, the issue above is gone.

With the same code as above, the generated styles will be:

@layer default {
  .MuiDialogTitle-root+.css-1foygqf-MuiDialogContent-root {
    padding-top: 0px;
  }
}

@layer sx {
  .css-1foygqf-MuiDialogContent-root {
    padding-top: 16px; <<< This wins regardless of the injection order ✅
  }
}

Side benefits

Independent of styles injection order. With modern framework like Next.js evolves, the style injection order might not be straightforward which could produce styles inconsistency for Emotion.

By moving to CSS layers, we don't need to worry about the injection order because CSS will take care of it. Just need to make sure that the layer order is always at the top of the head.

Implementation

  1. When the theme has the flag (modularCssLayers) enabled, Material UI serialize incoming styles (turns object, array, function) to a string and wrap them with @layer layerName. Since the serialized styles come from Emotion, this works perfectly fine.
image

The styles are split into 5 layers:

  • @layer global: for styles coming from GlobalStyles component (CssBaseline too)
  • @layer default: the base styles coming from Material UI components.
  • @layer theme: the component styles from createTheme().
  • @layer custom: styles from styled() that does not have Mui name prefix.
  • @layer sx: styles from the sx prop.

There is no performance downside because the serialization has to be done anyway. When Emotion receive the serialized styles, it will skip the serialization and go through injection process.

  1. Material UI automatically attaches layer order style tag to the document's head first child when modularCssLayers is enabled:
<head>
  <style data-mui-css-layer="…" >@layer mui.global, mui.default, mui.theme, mui.custom, mui.sx;</style>
</head>
Before After
image image

@siriwatknp siriwatknp added the scope: system The system, the design tokens / styling foundations used across components. eg. @mui/system with MUI label Apr 25, 2025
@mui-bot
Copy link

mui-bot commented Apr 25, 2025

Netlify deploy preview

Bundle size report

@mui/materialparsed: 🔺+1.72KB(+0.32%) gzip: 🔺+482B(+0.31%)
@mui/labparsed: 🔺+580B(+0.38%) gzip: 🔺+179B(+0.36%)
@mui/systemparsed: 🔺+1.64KB(+2.05%) gzip: 🔺+588B(+1.99%)
@mui/utilsparsed: 0B(0.00%) gzip: 0B(0.00%)

Details

Show details for 100 more bundles (86 more not shown)

@mui/material/CssBaselineparsed: 🔺+8KB(+12.66%) gzip: 🔺+3.66KB(+16.68%)
@mui/material/GlobalStylesparsed: 🔺+8KB(+13.33%) gzip: 🔺+3.6KB(+17.36%)
@mui/material/Selectparsed: 🔺+907B(+0.64%) gzip: 🔺+302B(+0.64%)
@mui/material/TablePaginationparsed: 🔺+907B(+0.52%) gzip: 🔺+288B(+0.51%)
@mui/material/TextFieldparsed: 🔺+907B(+0.59%) gzip: 🔺+271B(+0.54%)
@mui/material/OutlinedInputparsed: 🔺+862B(+0.96%) gzip: 🔺+320B(+1.01%)
@mui/material/NativeSelectparsed: 🔺+860B(+0.95%) gzip: 🔺+296B(+0.92%)
@mui/material/FilledInputparsed: 🔺+810B(+0.92%) gzip: 🔺+313B(+1.00%)
@mui/material/Inputparsed: 🔺+810B(+0.95%) gzip: 🔺+315B(+1.02%)
@mui/material/InputBaseparsed: 🔺+810B(+0.98%) gzip: 🔺+276B(+0.92%)
@mui/material/Radioparsed: 🔺+701B(+0.74%) gzip: 🔺+199B(+0.59%)
@mui/material/Checkboxparsed: 🔺+620B(+0.66%) gzip: 🔺+186B(+0.55%)
@mui/material/Switchparsed: 🔺+620B(+0.67%) gzip: 🔺+190B(+0.58%)
@mui/material/Breadcrumbsparsed: 🔺+610B(+0.65%) gzip: 🔺+192B(+0.58%)
@mui/material/SwipeableDrawerparsed: 🔺+595B(+0.57%) gzip: 🔺+185B(+0.51%)
@mui/material/Sliderparsed: 🔺+583B(+0.64%) gzip: 🔺+183B(+0.57%)
@mui/material/Ratingparsed: 🔺+579B(+0.72%) gzip: 🔺+170B(+0.58%)
@mui/material/IconButtonparsed: 🔺+578B(+0.64%) gzip: 🔺+174B(+0.54%)
@mui/material/Tabparsed: 🔺+578B(+0.66%) gzip: 🔺+196B(+0.63%)
@mui/material/TableSortLabelparsed: 🔺+578B(+0.64%) gzip: 🔺+192B(+0.60%)
@mui/material/TabScrollButtonparsed: 🔺+578B(+0.65%) gzip: 🔺+185B(+0.58%)
@mui/material/AccordionSummaryparsed: 🔺+577B(+0.65%) gzip: 🔺+194B(+0.61%)
@mui/material/BottomNavigationActionparsed: 🔺+577B(+0.66%) gzip: 🔺+195B(+0.62%)
@mui/material/ButtonBaseparsed: 🔺+577B(+0.69%) gzip: 🔺+181B(+0.60%)
@mui/material/CardActionAreaparsed: 🔺+577B(+0.66%) gzip: 🔺+190B(+0.61%)
@mui/material/Collapseparsed: 🔺+577B(+0.75%) gzip: 🔺+157B(+0.56%)
@mui/material/Fabparsed: 🔺+577B(+0.66%) gzip: 🔺+193B(+0.62%)
@mui/material/ListItemButtonparsed: 🔺+577B(+0.66%) gzip: 🔺+193B(+0.62%)
@mui/material/MenuItemparsed: 🔺+577B(+0.66%) gzip: 🔺+202B(+0.64%)
@mui/material/StepContentparsed: 🔺+577B(+0.72%) gzip: 🔺+179B(+0.62%)
@mui/material/ToggleButtonparsed: 🔺+577B(+0.66%) gzip: 🔺+200B(+0.64%)
@mui/lab/LoadingButtonparsed: 🔺+576B(+0.59%) gzip: 🔺+188B(+0.56%)
@mui/lab/Masonryparsed: 🔺+576B(+0.79%) gzip: 🔺+193B(+0.73%)
@mui/lab/TabListparsed: 🔺+576B(+0.57%) gzip: 🔺+199B(+0.55%)
@mui/material/Alertparsed: 🔺+576B(+0.57%) gzip: 🔺+179B(+0.51%)
@mui/material/Badgeparsed: 🔺+576B(+0.75%) gzip: 🔺+183B(+0.67%)
@mui/material/Buttonparsed: 🔺+576B(+0.59%) gzip: 🔺+192B(+0.58%)
@mui/material/FormControlLabelparsed: 🔺+576B(+0.76%) gzip: 🔺+162B(+0.59%)
@mui/material/StepButtonparsed: 🔺+576B(+0.61%) gzip: 🔺+192B(+0.58%)
@mui/material/StepLabelparsed: 🔺+576B(+0.75%) gzip: 🔺+194B(+0.70%)
@mui/material/Tabsparsed: 🔺+576B(+0.57%) gzip: 🔺+190B(+0.53%)
@mui/lab/TabPanelparsed: 🔺+575B(+0.83%) gzip: 🔺+171B(+0.69%)
@mui/lab/Timelineparsed: 🔺+575B(+0.83%) gzip: 🔺+167B(+0.67%)
@mui/lab/TimelineConnectorparsed: 🔺+575B(+0.84%) gzip: 🔺+165B(+0.67%)
@mui/lab/TimelineContentparsed: 🔺+575B(+0.79%) gzip: 🔺+162B(+0.62%)
@mui/lab/TimelineDotparsed: 🔺+575B(+0.83%) gzip: 🔺+177B(+0.71%)
@mui/lab/TimelineItemparsed: 🔺+575B(+0.82%) gzip: 🔺+177B(+0.70%)
@mui/lab/TimelineOppositeContentparsed: 🔺+575B(+0.79%) gzip: 🔺+170B(+0.65%)
@mui/lab/TimelineSeparatorparsed: 🔺+575B(+0.84%) gzip: 🔺+165B(+0.67%)
@mui/material/Accordionparsed: 🔺+575B(+0.69%) gzip: 🔺+180B(+0.60%)
@mui/material/AccordionActionsparsed: 🔺+575B(+0.84%) gzip: 🔺+184B(+0.74%)
@mui/material/AccordionDetailsparsed: 🔺+575B(+0.84%) gzip: 🔺+185B(+0.74%)
@mui/material/AlertTitleparsed: 🔺+575B(+0.80%) gzip: 🔺+173B(+0.67%)
@mui/material/AppBarparsed: 🔺+575B(+0.78%) gzip: 🔺+187B(+0.71%)
@mui/material/Avatarparsed: 🔺+575B(+0.76%) gzip: 🔺+172B(+0.63%)
@mui/material/AvatarGroupparsed: 🔺+575B(+0.75%) gzip: 🔺+174B(+0.62%)
@mui/material/Backdropparsed: 🔺+575B(+0.74%) gzip: 🔺+183B(+0.66%)
@mui/material/BottomNavigationparsed: 🔺+575B(+0.83%) gzip: 🔺+175B(+0.70%)
@mui/material/ButtonGroupparsed: 🔺+575B(+0.77%) gzip: 🔺+190B(+0.73%)
@mui/material/Cardparsed: 🔺+575B(+0.81%) gzip: 🔺+163B(+0.64%)
@mui/material/CardActionsparsed: 🔺+575B(+0.84%) gzip: 🔺+182B(+0.73%)
@mui/material/CardContentparsed: 🔺+575B(+0.84%) gzip: 🔺+185B(+0.74%)
@mui/material/CardHeaderparsed: 🔺+575B(+0.76%) gzip: 🔺+176B(+0.65%)
@mui/material/CardMediaparsed: 🔺+575B(+0.83%) gzip: 🔺+183B(+0.73%)
@mui/material/Chipparsed: 🔺+575B(+0.59%) gzip: 🔺+171B(+0.50%)
@mui/material/CircularProgressparsed: 🔺+575B(+0.77%) gzip: 🔺+186B(+0.69%)
@mui/material/Containerparsed: 🔺+575B(+0.82%) gzip: 🔺+185B(+0.73%)
@mui/material/Dialogparsed: 🔺+575B(+0.61%) gzip: 🔺+191B(+0.57%)
@mui/material/DialogActionsparsed: 🔺+575B(+0.84%) gzip: 🔺+181B(+0.72%)
@mui/material/DialogContentparsed: 🔺+575B(+0.83%) gzip: 🔺+179B(+0.71%)
@mui/material/DialogContentTextparsed: 🔺+575B(+0.80%) gzip: 🔺+182B(+0.70%)
@mui/material/DialogTitleparsed: 🔺+575B(+0.80%) gzip: 🔺+177B(+0.68%)
@mui/material/Dividerparsed: 🔺+575B(+0.80%) gzip: 🔺+184B(+0.71%)
@mui/material/Drawerparsed: 🔺+575B(+0.59%) gzip: 🔺+183B(+0.53%)
@mui/material/FormGroupparsed: 🔺+575B(+0.83%) gzip: 🔺+181B(+0.72%)
@mui/material/FormHelperTextparsed: 🔺+575B(+0.82%) gzip: 🔺+169B(+0.66%)
@mui/material/FormLabelparsed: 🔺+575B(+0.82%) gzip: 🔺+180B(+0.70%)
@mui/material/Gridparsed: 🔺+575B(+0.77%) gzip: 🔺+169B(+0.63%)
@mui/material/GridLegacyparsed: 🔺+575B(+0.78%) gzip: 🔺+174B(+0.66%)
@mui/material/Iconparsed: 🔺+575B(+0.82%) gzip: 🔺+183B(+0.72%)
@mui/material/ImageListparsed: 🔺+575B(+0.83%) gzip: 🔺+175B(+0.70%)
@mui/material/ImageListItemparsed: 🔺+575B(+0.82%) gzip: 🔺+196B(+0.77%)
@mui/material/ImageListItemBarparsed: 🔺+575B(+0.81%) gzip: 🔺+165B(+0.64%)
@mui/material/InputAdornmentparsed: 🔺+575B(+0.79%) gzip: 🔺+174B(+0.66%)
@mui/material/InputLabelparsed: 🔺+575B(+0.78%) gzip: 🔺+168B(+0.64%)
@mui/material/LinearProgressparsed: 🔺+575B(+0.73%) gzip: 🔺+208B(+0.76%)
@mui/material/Linkparsed: 🔺+575B(+0.77%) gzip: 🔺+188B(+0.70%)
@mui/material/Listparsed: 🔺+575B(+0.83%) gzip: 🔺+174B(+0.69%)
@mui/material/ListItemparsed: 🔺+575B(+0.79%) gzip: 🔺+168B(+0.64%)
@mui/material/ListItemAvatarparsed: 🔺+575B(+0.84%) gzip: 🔺+190B(+0.76%)
@mui/material/ListItemIconparsed: 🔺+575B(+0.83%) gzip: 🔺+185B(+0.74%)
@mui/material/ListItemSecondaryActionparsed: 🔺+575B(+0.83%) gzip: 🔺+188B(+0.75%)
@mui/material/ListItemTextparsed: 🔺+575B(+0.76%) gzip: 🔺+177B(+0.65%)
@mui/material/ListSubheaderparsed: 🔺+575B(+0.82%) gzip: 🔺+182B(+0.72%)
@mui/material/MenuListparsed: 🔺+575B(+0.80%) gzip: 🔺+185B(+0.70%)
@mui/material/MobileStepperparsed: 🔺+575B(+0.67%) gzip: 🔺+181B(+0.61%)
@mui/material/Modalparsed: 🔺+575B(+0.65%) gzip: 🔺+183B(+0.58%)
@mui/material/Paginationparsed: 🔺+575B(+0.59%) gzip: 🔺+184B(+0.54%)
@mui/material/PaginationItemparsed: 🔺+575B(+0.60%) gzip: 🔺+161B(+0.48%)
@mui/material/Paperparsed: 🔺+575B(+0.82%) gzip: 🔺+174B(+0.68%)

Details of bundle changes

Generated by 🚫 dangerJS against c170244

@siriwatknp siriwatknp changed the title [system] Add experimental_modularCssLayers theme flag to split styles into multiple CSS layers [system] Add modularCssLayers theme flag to split styles into multiple CSS layers Jun 6, 2025
Copy link
Contributor

@mapache-salvaje mapache-salvaje left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @siriwatknp ! Very cool feature!

siriwatknp and others added 4 commits June 11, 2025 10:07
Co-authored-by: mapache-salvaje <71297412+mapache-salvaje@users.noreply.github.com>
Co-authored-by: Marija Najdova <mnajdova@gmail.com>
Signed-off-by: Siriwat K <siriwatkunaporn@gmail.com>
Copy link
Contributor

@mapache-salvaje mapache-salvaje left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more suggestions here to sharpen the Caveats section but otherwise I think the doc's ready to go!

Co-authored-by: mapache-salvaje <71297412+mapache-salvaje@users.noreply.github.com>
Signed-off-by: Siriwat K <siriwatkunaporn@gmail.com>
@siriwatknp siriwatknp merged commit 37d7020 into mui:master Jun 12, 2025
23 checks passed
siriwatknp added a commit to siriwatknp/material-ui that referenced this pull request Jun 12, 2025
…ple CSS layers (mui#46001)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: system The system, the design tokens / styling foundations used across components. eg. @mui/system with MUI type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

6 participants