Is there a way to support prefers-color-scheme in the future? I guess, it would be possible for every project to make a custom implementation to support it, but maybe it could be a good idea to add support for it by default.
The CSS media feature lets you use a media query to adapt your styling to the light or dark theme requested by the system. If the user's browser is using dark theme, the application could adapt to it and use dark theme aswell, instead of showing an eye-hurting white.
We currently use this media query in our docs to switch to a dark theme on the client. Since we prerender them we always deliver the version with the light theme that gets hydrated with the preferred color scheme causing a flash of outdated styles if a dark theme is preferred.
As far as I know the only solution would be to use the CSS media query in our core components which would use the colors in the dark theme. This would add a lot of styles to the core for a query that isn't supported by most of our supported platforms or even some operating systems.
I think it's better to write a custom theme that adds the query to all the necessary classes concerning color (which is probably a lot). I guess this is one of those instances where CSS variables would be the best solution (if they can read media queries).
Notice the introduction of new section in the documentation: https://material-ui.com/customization/palette/#user-preference
import React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { ThemeProvider } from '@material-ui/core/styles';
function App() {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme = React.useMemo(
() =>
createMuiTheme({
palette: {
type: prefersDarkMode ? 'dark' : 'light',
},
}),
[prefersDarkMode],
);
return (
<ThemeProvider theme={theme}>
<Routes />
</ThemeProvider>
);
}
This topic is close to the discussion we have in #16367.
We have the odd situation where useMediaQuery
always fails during the first render, causing an incorrect flash of unstyled content in some cases. The following ugly workaround works, because it should not be the case that both media queries fail.
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
const theme = useTheme(prefersDarkMode)
// Refuse to render until media queries align
const prefersLightMode = useMediaQuery('(prefers-color-scheme: light)')
if (prefersDarkMode !== !prefersLightMode) return null
The first render both fail, so it refuses to render anything, then it soon corrects and renders a logically consistent result (only one of prefers light or prefers dark) and we get no incorrectly styled flash.
@elyobo Your workaround is equivalent to the usage of the NoSsr
component, right?
I have no idea, haven't looked at any of the SSR stuff because we're not doing SSR, the issue is happening for us client side. First render both are false, then very soon after it renders in a sane way (can't prefer both light and dark).
Super interesting, thanks for sharing, I think that we should run the update on the layout effect. +1 for a PR.
While looking at the code to understand what you were suggesting, I noticed this
const {
defaultMatches = false,
matchMedia = supportMatchMedia ? window.matchMedia : null,
noSsr = false,
ssrMatchMedia = null,
} = {
...props,
...options,
};
const [match, setMatch] = React.useState(() => {
if (noSsr && supportMatchMedia) {
return matchMedia(query).matches;
}
if (ssrMatchMedia) {
return ssrMatchMedia(query).matches;
}
// Once the component is mounted, we rely on the
// event listeners to return the correct matches value.
return defaultMatches;
});
Our problem can be solved by passing in noSsr: true
to useMediaQuery
(doesn't seem to be documented), because we're not using SSR, and without passing through that flag the default is defaultMatches
(which itself defaults to false). This matches what we're seeing (everything is false at first).
Perhaps the PR needed is a documentation one?
If you were thinking of using useLayoutEffect
instead of useEffect
it doesn't look like that would solve our problem, which is just that it's using the default value initially rather than actually running the check.
causing an incorrect flash of unstyled content in some cases
@elyobo I had this problem in mind, I would expect useLayoutEffect
to solve the problem. If you have a reproduction, it would help to investigate.
@oliviertassinari our problem is that unless you pass noSsr
it uses the defaultMatches
value as the initial value of the state. useLayoutEffect
might help, but it's not necessary for us if we can just get the match to work correctly on the first run instead, and you'd still be causing an unnecessary render I think (it would still generate the theme twice, just that the second one would happen earlier than currently, right?).
@elyobo Ok, so we might not be able to do anything about it, noSsr
seems to be the way.
Ok if I make a PR to extend the docs for some of those options?
On Sat, 21 Dec 2019, 08:41 Olivier Tassinari, notifications@github.com
wrote:
@elyobo https://github.com/elyobo Ok, so we might not be able to do
anything about it, noSsr seems to be the way.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/mui-org/material-ui/issues/15588?email_source=notifications&email_token=AADDR7QBMJ5J7FAXPF4YQYLQZU3Y5A5CNFSM4HKZBV6KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEHOH3GI#issuecomment-568098201,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AADDR7RHJPOR3RGHGSP4OELQZU3Y5ANCNFSM4HKZBV6A
.
Sure, it would help
Hey! I just faced this issue and I had come across this blog post. The approach suggested here seems to be to use CSS Custom Properties
along with script to detect whether the user has a color preference on initial load.
Theme-UI seems to do this and also provides a useColorMode
hook that allows a user to toggle all the color modes which remembers preference using local storage. Material-UI could take a similar approach?
I would not mind working on this if you guys think it is a valid approach. It would be my first open source issue though so any help would be appreciated!😅
@HiranmayaGundu have you tried using theme-ui with material-ui? I've been trying to find examples but haven't found one.
@bugzpodder No I have not tried that out, and I'm not entirely sure that is possible? Material-UI uses it's own theme specification. You could use createMuiTheme()
to make a custom theme
I'm using Next.js and I've wrapped my <App>
component with the <ThemeProvider>
, which should be preserving the theme between pages, but I'm still noticing a flash of light
-> dark
between pages.
Am I or Next.js screwing something up, or is there still not a concrete solution to this flash of unthemed changes?
Another note: Using the CSS media query would be a big pain to write, but would fix this issue, as well as using dynamic themes with AMP. This article is a good read on how a Gatsby site had a dark theme implemented using CSS variables in order to prevent any FoUC.
@pizzafox The flash comes from the reliance on JavaScript to detect the media query. To solve the flash, there are a couple of options:
@oliviertassinari Is it possible to allow strings like var(--color-red)
in the MUI Theme? That would allow me to use CSS variables just for the palette.
@HiranmayaGundu We have a running discussion about it in #12827.
We can do something like:
// checking if the media query is true
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const theme = createMuiTheme({ palette: { type: colorScheme } });
to achieve dark theme based on browser/system settings.
Most helpful comment
Notice the introduction of new section in the documentation: https://material-ui.com/customization/palette/#user-preference
This topic is close to the discussion we have in #16367.