-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
[RFC] CSS theme variables support #27651
Comments
@siriwatknp Notes:
|
Obviously we would need to revisit how we use the theme. There wasn't a need before but if we can enable not re-rendering then we should try it. I suspect that for dark mode (which only affects color) we can probably get this fairly easy by splitting contexts. |
@eps1lon Agree. To be clear on where I was coming from, I was trying to consider what will happen with: |
Would it be possible to add a SHA hash to |
I think it should be possible because the <body>
{React.cloneElement(getInitColorSchemeScript({ enableSystem: true }), {
// ...any props
})}
<Main />
</body> |
I'm using the following function in my import { renderToStaticMarkup } from 'react-dom/server.js';
import { getInitColorSchemeScript } from '@mui/material';
import crypto from 'crypto';
function getMuiInitColorSchemeScriptHash() {
const muiInitColorSchemeScriptElement = renderToStaticMarkup(
getInitColorSchemeScript(),
);
const muiInitColorSchemeScript = /<script>([\s\S]*?)<\/script>/i.exec(
muiInitColorSchemeScriptElement,
)[1];
return crypto
.createHash('sha256')
.update(muiInitColorSchemeScript)
.digest('base64');
} This could be simplified, since the script is already a string in the getInitColorSchemeScript(). Maybe it could return the It might also be a good idea to minify the script content before setting the innerHTML using something like terser. |
Agree! |
Since CSS variables support is still an experiment, I want to propose another way of using CSS variables from the theme. CurrentThe eg.
console.log(theme.vars.palette.primary.main) // var(--mui-palette-primary-main) There are 2 things that pop into my head when migrating the mui docs to use CSS variables.
const StyledComponent = styled('button')(({ theme }) => ({
color: theme.vars.palette.brand.main,
}) If you put another theme that does not have color: theme.vars.palette.primary.main.replace(/\)/, ', var(--custom-color))')
// so it becomes `var(--mui-palette-primary-main, var(--custom-color))`
// this is how CSS variables work for providing a fallback
// https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#custom_property_fallback_values Using a function insteadWe have already attached a function called console.log(theme.getCssVar('palette-primary-main')) // var(--mui-palette-primary-main) so the implementation will be like this: const StyledComponent = styled('button')(({ theme }) => ({
color: theme.getCssVar('palette-primary-main'),
}) Benefits:
theme.getCssVar('palette-primary-main', 'var(--custom-color)') Note: this proposal does not change the way you create the theme, it just changes the way you get the variable. If this sounds good, we will remove the |
Leaving some comments on the pros/cons, I am not sure what is the best way going forward, I feel like we should do a poll about it at some point to see what the community would vote for. On the current approach problems:
I wouldn't say this is necessary bad, as you would like to catch these errors before going to production. Silently crashing may be worse, as in big apps it's hard to validate all screens and all different states.
Agree, we anyway need to have some util to help here I guess :) |
After transitioning to the
|
Can you open a new issue with reproduction? You can tag me, I will take a look. |
It is live in the documentation as experimental APIs: https://mui.com/material-ui/experimental-api/css-theme-variables/overview/, so I'm closing this RFC! |
Has anyone got this working with next.js 14 app router yet, without any content flashing? I've been racking my brain over this trying all kinds of different combinations of the mui instructions for nextjs and the experimental CSS theme variables method. I either get a dark theme loading correctly without flashing, but then when I try the light theme it will load correctly after 1 refresh of the page, but then on the second refresh, it flashed the dark theme shortly, AFTER initially loading correctly and then go back to the correct one. And vice-versa. I've tried reading cookies with nextjs on the server to correctly SSR the layout, which can serve the correct themed components (after the first load has set the cookie), which seems to be a nice solution, but its not perfect when the user blocks cookies. Is there something about the experimental CSS theme vars method that does this, or is it possibly nextjs that messes this up? I've also tried using Any existing examples or sandboxes using either the |
@TrySpace Can you provide a repository that I can take a look? |
Hey I have the same problem, you can check out my repo for an example. https://github.com/MikeLautensack/Estimate-Generator/tree/features use features branch (current working branch) if you want to run it locally. Thank you |
Problems
Dark theme flash on SSR (server-side rendering)
One obvious example is material-ui documentation. If you toggle dark theme and press hard refresh, you will see a flash of light mode before it turns dark.
Bad debugging experience
the value in dev tool is the color code ex.
#f5f5f5
which is hard to know which design token the component is using. This gives bad experience to both developer (working locally) and non-developer (checking the color in production website)Performance
the current implementation consider light & dark as a separate theme which means by toggling to another mode will cause component in the tree to rerender. (However, the impact is not significant ~100-300ms depends on how large the page is)
Solution
Replace runtime calculation of palette inside the component with css variable reference.
Now, the component stylesheet does not change between render because the
backgroundColor
andcolor
are just string. To achieve perfect dark mode, all themes must be prepared at build time because we want to set the correct mode before browser render the DOMs.This is a simple script that have to be placed at the top of
<body>
(before main React script) to set the user's selected mode.Then browser renders the DOMs and paint with correct styles (because of css specificity).
Summary: css variables allow us prepare stylesheet with different themes at build time so that we can apply the user's selected theme (on the client) before rendering the whole application.
Benefits
CSS Variables open many doors for improving DX and design opportunities.
trueBlack
comfort
)<ThemeProvider theme={darkTheme}>
<div data-theme="dark">
will behave as dark mode regardless of user's preference.does not cause the whole application to rerender.Requirements & Features
check out the POC #28639
Required
@mui/material
so that the migration afford is acceptable (breaking change is expected because the implementation in the components has to change.Configurable
$colorScheme
can be configured[data-*="$colorScheme"]
) can be configured or change to class selector--md-palette-...
or--joy-palette-...
)This is a diagram that shows the high level of what the change looks like
Developer needs to implement a state to store user-selected mode and sync when their preference change then recalculates the theme and pass to
MUI
ThemeProvider.MUI
takes control of user-selectedmode
and provides the APIs for developers to configure & input multiple themes, reading & setting mode via react hook.API interface
The challenge of css variables support is the integration with both the 2nd design system we are working on and the existing material-design components (aka
@mui/material
). To make it easier to maintain and able to work alongside other products, the css variables support should be a dedicated module that exposes a few APIs for a design system.CssVarsProvider
(useThemeProvider
internally)vars
to theme so the component can reference to css variable with object notationmode
state and provide hook for changing mode on the client.code
useColorScheme
A hook to get the current mode and
setColorScheme
function to change the mode.getInitColorSchemeScript
Call this function at the top of the to set the initial color scheme before the DOM is rendered on screen. eg. with Nextjs
the script will check the local storage and apply the right mode to DOM before the application is rendered.
Decision on the APIs
What's the
vars
structure?***This might change, please read the proposal
The
vars
is an object that references to CSS variable. This object is created from theme input and then we need to attach it to the theme so that the components can access this object.✅ Option 1: same input structure (nested)
Option 2: replace the theme with vars and move theme input inside another keyOption 3: flattenThis option is hard for@mui/material
to migrateNeed verification
bundling all the themes
andapplying one theme at a time
. (with 100 variables like--mui-palette-primary-main: ...
gain ~3.5kB)Plan & Progress
system
(could be moved to its own package later). [system] Addunstable_createCssVarsProvider
api #28965$colorScheme
can be configured[data-*="$colorScheme"]
can be configured or change to class selector--md-palette-...
or--joy-palette-...
)mode
('light' | 'dark') fromcolorScheme
('light' | 'dark' | ...) (inspired by Primer)colorScheme
for day & night mode@mui/material
(after the module is settled) [RFC] Strategy for adopting CSS variables in `@mui/material` #30485alpha
lighten
, ...)cssVars
(names might be changed)Related issues
Resources & Inspiration
The text was updated successfully, but these errors were encountered: