Skip to content

[material-nextjs] Add option to enable CSS layers for pages router #45596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Apr 11, 2025

Conversation

siriwatknp
Copy link
Member

@siriwatknp siriwatknp commented Mar 17, 2025

Docs: https://deploy-preview-45596--material-ui.netlify.app/material-ui/integrations/nextjs/#pages-router

closes #45220

part of #44700

Motivation

To unblock Tailwind CSS v4 integration. Only pages router is missing this option.

Next.js App router and Vite (via StyledEngineProvider) are already supported.

Changes for @mui/material-nextjs

export createEmotionCache

Add option enableCssLayer to pages router createEmotionCache and reexport it.
The logic of CSS layer wrapping is the same as App Router and StyledEngineProvider.

update SSR emotion styles

Required to make it work with Tailwind CSS v4 or other CSS layer implementation.
The setup requires a GlobalStyles component to configure the order of layers.

<GlobalStyles styles="@layer theme, base, mui, components, utilities" />
// mui = Material UI component layer, the rest are default Tailwind CSS layers.
// This config makes the utilities classes override Material UI components without `!important`.

// The <GlobalStyles> needs to come before any Material UI components, so that it's injected first

This is the before:

Screen.Recording.2568-03-17.at.15.31.54.mov

In summary, the flicker occurs at hydration phase because Emotion hydrates at cache creation step.

Emotion hydrates only <style data-emotion="mui *"> (see the file) which does not include global styles, causing the CSS layer order declaration to appear below component styles.

Here is what happens chronologically:

  1. on the server ✅
// on the server, the order is correct
<style data-emotion="mui-global …">@layer theme, base, mui, components, utilities;<style>
<style data-emotion="mui …">@mui { .MuiButton-…}</style>
  1. at hydration phase ❌
// at hydration phase, the component styles appear first because of Emotion hydration logic
<style data-emotion="mui …">@mui { .MuiButton-…}</style>
…
<style data-emotion="mui-global …">@layer theme, base, mui, components, utilities;<style>
<style>
 // Tailwind CSS
 @layer theme, base, components, utilities;
 </style>

So, at hydration phase, Tailwind base styles will override Material UI components causing the flicker.

  1. After the hydration phase, the flicker disappear because the GlobalStyles renders and the layer order is moved the top of the component styles. ✅
// after hydration, first render
<style data-emotion="mui-global …">@layer theme, base, mui, components, utilities;<style>
<style data-emotion="mui …">@mui { .MuiButton-…}</style>

After updating the file, there is no flicker because the layer order is always at the top before any component/global styles.

Screen.Recording.2568-03-17.at.15.25.25.mov

Upgrade Tailwind CSS to v4

Upgrading Tailwind CSS to v4 to dogfood the API added to @mui/material-nextjs and to experience the impact on adopting CSS layer.

By moving to CSS layer, I have to update the useLazyCSS to wrap the docsearch cdn CSS with @layer docsearch too, otherwise it won't be overridden by Material UI styled.


@siriwatknp siriwatknp added package: material-ui Specific to @mui/material nextjs labels Mar 17, 2025
@mui-bot
Copy link

mui-bot commented Mar 17, 2025

Netlify deploy preview

Bundle size report

No bundle size changes (Toolpad)
No bundle size changes

Generated by 🚫 dangerJS against e7de780

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 18, 2025
@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged and removed PR: out-of-date The pull request has merge conflicts and can't be merged labels Mar 18, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 18, 2025
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 21, 2025
@siriwatknp siriwatknp marked this pull request as ready for review March 21, 2025 07:55
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 21, 2025
Comment on lines +1 to +2
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
Copy link
Member Author

@siriwatknp siriwatknp Mar 21, 2025

Choose a reason for hiding this comment

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

Based on the Tailwind CSS docs for disabling preflight

@tailwind utilities;
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/utilities.css' layer(utilities);
@config '../tailwind.config.mjs';
Copy link
Member Author

Choose a reason for hiding this comment

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

To use the plugin define in the config file (Tailwind v3)

Copy link
Member

@DiegoAndai DiegoAndai left a comment

Choose a reason for hiding this comment

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

Only one question on the @mui/material-next-js side, I would request @alexfauquette's review for the docs-infra side.

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 9, 2025
@siriwatknp siriwatknp requested a review from alexfauquette April 9, 2025 07:39
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 9, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 9, 2025
@siriwatknp siriwatknp requested a review from DiegoAndai April 9, 2025 09:32
Copy link
Member

@DiegoAndai DiegoAndai left a comment

Choose a reason for hiding this comment

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

Looks good to me from the @mui/material-nextjs side. I'm not approving because we need a review from @mui/docs-infra for the docs side, but feel free to merge when that's done.

@siriwatknp siriwatknp added the scope: docs-infra Specific to the docs-infra product label Apr 10, 2025
Copy link
Member

@alexfauquette alexfauquette left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. Take this approval with a grain of salt, I never used TailwindCSS on a personal project, so I do not know the subtility between v3 and v4 at all

let styleElement: HTMLStyleElement | null = null;
const abortController = new AbortController();

// Fetch the CSS content directly to avoid CORS issues with cssRules
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand how we could get CORS issues. Everything comes from the same origin 🤔

But more security do not hurt

@siriwatknp siriwatknp merged commit 6dfff66 into mui:master Apr 11, 2025
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nextjs package: material-ui Specific to @mui/material scope: docs-infra Specific to the docs-infra product
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[docs] Migrate to Tailwind CSS v4
4 participants