Skip to content

[poc] Support a version of Material UI without CSS in JS#44407

Draft
mnajdova wants to merge 144 commits into
mui:masterfrom
mnajdova:poc/styling-v8
Draft

[poc] Support a version of Material UI without CSS in JS#44407
mnajdova wants to merge 144 commits into
mui:masterfrom
mnajdova:poc/styling-v8

Conversation

@mnajdova

@mnajdova mnajdova commented Nov 14, 2024

Copy link
Copy Markdown
Member

This PR serves as a proof of concept of how we can build Material UI components using plain CSS, but still allow CSS-in-JS users to use all of the previously available APIs, like sx prop, styleOverrides etc if they want to. On the other hand, for users that care about performance or want to use plain CSS/Tailwind CSS for customisation, they don't have to pay any penalty of bundle size or performance that comes from using CSS-in-JS.

The PR also adds the @mui/tailwind package that hosts plugins and theme overrides for Tailwind to make it easier to test out. We could extract this package and publish it independently.

Finally, the PR also tests how using only a css file, that's build from the theme via a script (build:theme-css), could be used instead of using the theme provider. This css file has all the variables, while each component (such as the Slider) use these variables directly in their respective css files. At the moment, the css file is built in buildDefaultThemeCss.mts where it uses a default theme, generateStyleSheets and styleSheetsToString to stringify the result. We can improve this last part such that consumers could generate the css files from custom themes, even though they could customize the theme in the file as well.

Experiments:

While testing, focus on the slider component. Inspect the elements and notice how now the styles are attached to the "utility" classes, e.g. MuiSlider-root etc. Emotion and Tailwind CSS overrides still work as expected, leveraging the @layer CSS directive.

Example projects:

Initial benchmark:

I did few performance benchmarks and the results are promising. I am comparing the updated Slider component that is created using plain CSS with the previous one that uses Emotion. On top of it I am adding the same customization (using Emotion) as current users have. There are the results I am getting:

Test case 1: Rendering 500 sliders without any customization

Emotion - commit time: 2.8s
Plain CSS - commit time: 0.9s (3.1 x faster)

Test case 2: Rendering 500 sliders using sx prop with one CSS property

Emotion - commit time: 2.8s
Plain CSS - commit time: 1.1s (2.54 x faster)

Test case 3: Rendering 500 sliders using a long sx prop (iOS Slider example)

Emotion - commit time: 3.3s
Plain CSS - 1.8 (1.83 x faster)

Conclusion: In the worst case scenario, the component was 1.8 times faster. The most common use-cases will likely lie somewhere between the Test case 2 and Test case 3, which is still a substantial improvement. For every component that don't need customisation, the gain is already huge (3 times faster). Again, this is a bit of an artificial example, that uses only one component as benchmark but we can already see the size of the impact.

Todos:

  • RTL support - while migrating components we could change the theme to define logical CSS properties, or add the RTL styles together by using a selector
  • dark/light mode support - we can prepare the styles similar to the CSS generated by theme.applyDarkMode
    • changed the tailwind and emotion experiments to have dark mode aware themes, also added mode switches.
    • their theme should have 'data' as colorSchemeSelector
    • add @custom-variant dark (&:where([data-mui-color-scheme='dark'], [data-mui-color-scheme='dark'] *)); to global.css for tailwind mapping
    • add a useMounted in the experiments because useColorScheme returns undefined on the server and an actual value on the client, and switching the value with the Switch will create a hydration mismatch warning.
  • CSS injection - we need a simplified CSSVarsInjector component that out of the generated theme creates stylesheet with the CSS variables
    We can use the theme.generateStyleSheets() for this, however, as the functions generates JS syntax stylesheet, I used this converter to convert it to string (very fast AI generated implementation). We can provide it as a util out of Material UI.
    • added CssVarsInjector to inject the css variables in the head component, using styleSheetsToString to stringify the value from theme.generateStyleSheets()
    • replaced GlobalStyles with CssVarsInjector in createCssVarsProvider to inject the variables
    • ThemeProviderWithVars is the one supplying CssVarsInjector into createCssVarsProvider.
  • Fix animations in the Slider component
    • added fallback values directly in the css for the --mui-transition-duration-shortest and --mui-transition-easing-easeInOut, according to the fallbacks found in createTransitions.js
  • Figure out how we want to provide the theme when Emotion is not used (still use context or maybe depend on singleton that people can import - the result of createTheme)
    • created CssThemeProvider that aims to replace ThemeProvider when Emotion is not used, but it will still import emotion core, via useThemeWithoutDefault.
    • decoupled emotion completely by removing it from useThemeWithoutDefault entirely.

@mui-bot

mui-bot commented Nov 14, 2024

Copy link
Copy Markdown

Netlify deploy preview

Bundle size report

Bundle size will be reported once CircleCI build #832201 finishes.

Generated by 🚫 dangerJS against fee13d7

@mnajdova mnajdova changed the title [poc] Styled engine swapping methodology [poc] Support a version of Material UI without CSS in JS Nov 14, 2024
@oliviertassinari

Copy link
Copy Markdown
Member

It looks similar to https://gist.github.com/oliviertassinari/5e9031104a86eb5c3325819e9a23857f#muistyled-engine-empty

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 21, 2025
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label May 18, 2026
@mnajdova

Copy link
Copy Markdown
Member Author

How is https://github.com/mnajdova/next-mui-css-modules-emotion becomes CSS modules? The Slider is importing plain Slider.css file without a classes object right?

At some point I switched to plain CSS, the plain CSS vs CSS modules is a discussion that we should have

Should v10 frame as "default still Emotion with possible opt-out" vs "CSS by default, opt-in to Emotion"?

Happy with the second proposal is more aggressive. From technical point of view, switching between the two shouldn't be too complicated.

It'd nice to have a tool to convert CSS-in-JS to plain CSS. I see a lot of benefits for development, maintenance, userland.

So far for me, AI was the tool :D But it was a bit buggy, I'll admit.


One thing that we will need to figure out is documentation, do we want to still show how you can override everything with Emotion & sx? Do we need a Tailwind CSS/Plain CSS demos? This could be huge amount of work, that we should plan on ahead of time.

@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: out-of-date The pull request has merge conflicts and can't be merged. scope: system The system, the design tokens / styling foundations used across components. eg. @mui/system with MUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants