Material-ui: Support CSS variables as theme option

Created on 10 Sep 2018  路  22Comments  路  Source: mui-org/material-ui

Right now we can provide a primary and secondary colour as Hex, RGBa and HSL. What would be great is that, if we can provide gradients for primary and secondary colours as well. I know this is outside of material design guidelines but subtle gradients really make the UI look vibrant while keeping a minimal interface.

Currently this is possible using withStyles, but the elegance in simply giving a gradient config in the theme configuration directly would be super useful.

What do you guys think?

discussion enhancement

Most helpful comment

@balazsorban44 Agree, we have started to discuss this advantage with @mnajdova. The challenge here is what do we do with color transformation functions? I guess we could have them all generated ahead of time or use prefixing, like fade(primary.main, 0.3) could turn into a --palette-primary-main-fade-0-3 CSS variable.

All 22 comments

@abhisheksarka Supporting this would probably mean we support theme colors as CSS variables use case. I have nothing against supporting it, quite the opposite! The limitation right now is that we are performing color manipulation in JavaScript. I'm not sure there is any quick away around this problem. cc @mbrookes.

It is actually very easy to provide javascript dynamism to CSS variables. And in a way that stays sort of semantic.

Say you have a color value graded on an hsla rotation, and that the dynamic part is the H parameter.

The static css would be written as such : color: hsla(var(--graded-value), 100, 100, 1);

Now of course javascript cannot go write that in CSS (It can, but it would remove the whole purpose of variables).

What you CAN do is use the inheritance hierarchy of the cascade.

So therefore, something like <p style="--graded-value: 92">text</p> will actually affect the value read by the CSS variable.

And javascript, especially in react, has no problem changing the value of inline styles. It is still an external declaration, as the computed use of the variable value stays in the CSS, but declared by the component as a style configuration, not a declaration.

It is often seen as wrong to declare inline styles, but in this case I see it more as a data-attribute use. This can save huge amounts of CSS, and allow a single style declaration to produce multiple results.

I have been using this method for a while to produce content with graded, dynamic values, and it is buttery smooth, as the DOM has the final word on its visual configuration, CSS just reads and apply it.

There is a caveat with dynamically rendered components not respecting the CSS variables cascade, but there are workarounds around it.

@rlacorne The original request was to somehow support this as a theme option. Inline styles are a different beast.

@oliviertassinari apologies I missed the original name-check. I have no idea how we could reconcile those two concerns.

It can absolutely be used for themes in the same way.

CSS has the :root {} decalaration, it's only a matter of injecting global css variables on the app css :root.

$my-color: #bada55;

:root { --my-color: #bada55 }, only the variable version is much more stronger in its use and tweakability for redefinitions.

We have scripts, that passing all the mui theme consts to css variables on app init

and we use them like this:

.foo {
  padding: calc(var(--spacing-unit) * 2px);
  transition: padding calc(var(--transitions-duration-shortest) * 1ms);
}

it's great!

one problem with some theme-functions. but we have no cases for them for now


Screenshot 2019-07-26 at 14 52 30

our current css variable provider looks like

export function asCssConsts (theme, path) {
  const themeConsts = require('./get-set').get(theme, path);

  return Object.entries(themeConsts).reduce((res, [key, val]) => {
    return Object.assign(res, { ['--' + `${ path }.${ key }`.replace(/\./g, '-')]: val });
  }, {});
}

const styles = (theme) => ({
  '@global :root': {
    '--app-background-color': '#f4f4f4',

    ...asCssConsts(theme, 'spacing'), // e.g. --spacing-unit
    ...asCssConsts(theme, 'transitions.duration'), // e.g. --transitions-duration-shortest
    ...asCssConsts(theme, 'transitions.easing'), // e.g. --transitions-easing-easeInOut
    ...asCssConsts(theme, 'shape'), // e.g. --transitions-shape-borderRadius
    ...asCssConsts(theme, 'zIndex'), // e.g. --zIndex-tooltip
// ...

But It's harder to pass the theme.typography and pallete rule-sets. Css variables connot solve this. So we're use jss with ThemeProvider for that cases

some css processors allow extending (css-modules we use doesn't support it good :( out of the box)

so some mui plugin can expose rule-sets for extending like:

.typography-body1 {
  color: var(--theme-typography-body1--color);
  font-family: var(--theme-typography-body1--fontFamily);
  /* ... */
}

might be it can be useful with some post-css plugin, that enables extending

.myText {
  extends: .typography-body1;
  color: red;
}

for one https://github.com/mui-org/material-ui/issues/12827#issuecomment-515418720 above

ok, mui@4 as I see already does something like I propose with extendable rule-sets.

But we don't want to use global selectors by default and we're gonna disable this new behaviour,
so it'll be good, if even w/o global selector we can use extendable rule-sets

hey guys
I had always using css variables in all my codes and for material components I always need to override colors which component setted them from theme (that is because I didn't wants to set theme and styles in my js files and I wants to do taht in my css files)
now I had do something to use css variables in material-theme like this
palette: { primary: { main: "rgba(var(--primary-color-tuple))", }, secondary: { main: "rgba(var(--secondary-color-tuple))", }, }
and I can override these two css variable where ever I want
but it would be great that you wouldn't check my string up there and then I could use `main: "var(--primary-color)" instead of using rgba function and it would be more better becoause of performance and that in this mode I could use any css color type (such as named or hex) that I want in my css variable instead of having a tuple of numbers

so is there any plan to let us using css variables easily?

sorry for bad english

it would be great that you wouldn't check my string up there

Do you mean the "unsupported color" warning?

now I had do something to use css variables in material-theme like this
palette: { primary: { main: "rgba(var(--primary-color-tuple))", }, secondary: { main: "rgba(var(--secondary-color-tuple))", }, }

That might bypass the warning, but the js color manipulator functions aren't going to work on var(--primary-color-tuple)

Hey! Is it possible to evaluate the colors that need to be manipulated via js at the ThemeProvider level? If so, you could generate all the colors at the ThemeProvider and then set those values as CSS Variables.

The only other approach I think of would be

const root = window.document.documentElement;;
const primaryColor=
      root.style.getPropertyValue('--primary-color');

But I think that would mean it is not SSR compatible.

@HiranmayaGundu createMuiTheme() returns a plain object, so yes, it's possible to read the colors directly. This demo is related https://material-ui.com/customization/components/#css-variables.

@oliviertassinari Sorry I think I misunderstood this issue 馃槄 I think this issue is to allow theme.pallete.primary.main to be passed as var(--pallete-primary-main)?

Whereas what I was thinking was passing theme.pallete.primary.main as '#FFFFFF' to ThemeProvider would internally set it as a CSS Variable and then the components would use var(--pallete-primary-main)

@HiranmayaGundu This could be cool. I think that we will consider this option for v5 (but there is a IE 11 implication to figure out, which is important for the Chinese market and old UC mobile devices).

Supporting CSS variables would also open up avoiding flickering of dark mode websites implemented in frameworks like Next.js or Gatsby! (This seems to be the case on the https://material-ui.com/ website itself! Eg. if you set dark mode and refresh, the page will briefly show up in light mode, and then change to dark mode, causing a flicker)

Problem demonstrated: https://joshwcomeau.com/css/css-variables-for-react-devs/#dark-mode-flash-fix

Solution with CSS variables: https://joshwcomeau.com/gatsby/dark-mode/

Just a thought, could the theme object created by createMuiTheme be "compiled" to CSS variables/classes, where it makes sense? (like colors to variables, overrides to CSS classes, etc.)

@balazsorban44 Agree, we have started to discuss this advantage with @mnajdova. The challenge here is what do we do with color transformation functions? I guess we could have them all generated ahead of time or use prefixing, like fade(primary.main, 0.3) could turn into a --palette-primary-main-fade-0-3 CSS variable.

Supporting CSS variables would also open up avoiding flickering of dark mode websites implemented in frameworks like Next.js or Gatsby! (This seems to be the case on the https://material-ui.com/ website itself! Eg. if you set dark mode and refresh, the page will briefly show up in light mode, and then change to dark mode, causing a flicker)

Problem demonstrated: https://joshwcomeau.com/css/css-variables-for-react-devs/#dark-mode-flash-fix

Solution with CSS variables: https://joshwcomeau.com/gatsby/dark-mode/

Just a thought, could the theme object created by createMuiTheme be "compiled" to CSS variables/classes, where it makes sense? (like colors to variables, overrides to CSS classes, etc.)

@balazsorban44
the closest u can get at this moment.... setting palette.background.default , it's the only option that can set as var(--background-default) and use Cssbaseline.

After which everything has to load after hasMount since the JSS takes one render cycle to show all the correct colours. (you have to implement the localStorage preference for dark mode or light mode as per joshwcomeau website.

e.g. This is my layout file which I use for all my pages.

const Layout = (props) => {
  const hasMounted = useHasMounted();

  if (!hasMounted) return null

  return (
    <>
      <Navigation />
        {props.children}
    </>
  );
}

There's a split second lag, but better than showing split second flicker.

hasMounted hook

import React, { useEffect, useState } from 'react'

export const useHasMounted = () => {
    const [hasMounted, setHasMounted] = useState(false);
    useEffect(() => {
      setHasMounted(true);
    }, []);

    return hasMounted;
}

Light Mode to Dark Mode then Refresh Twice to show no-flickering

EDIT: material-ui.com loads their jss injection before all the scripts and have a nicely implemented dark mode now, hopefully they can share their technique.

As far as i see, this could be as easy as:

import React from 'react';
import { withStyles } from '@material-ui/core';

const cssVariables = (theme) => ({
  '@global': {
    ':root': {
      '--color-primary': theme.palette.primary.main,
      '--color-secondary': theme.palette.secondary.main,
    }
  }
});

const RootComponent = () => {
  return <div>Whatever...</div>
}

export default withStyles(cssVariables, RootComponent);

Having in mind RootComponent initialises inside ThemeProvider.

@not-only-code That exposes select theme colors as CSS variables, but it's the opposite of what's being asked for.

@mbrookes What i try to expose here is if theme would provide such parameter, could be implemented easily as css variable, the same as any color or shadow (theme.shadows[10]). Which, at the same time, I personally think is a good practise to have control of what is exposed "globally".

May I ask what is the status here ? Will v5 offer the ability to ideally do things like e.g. :

const createTheme = () => ({
  palette: {
    primary: {
      main: "var(--primary-color)",
    },
    secondary: {
      main: "var(--secondary-color)",
    },

so CSS variables can be used to solve the FOUC in dark mode switching (for instance in Gatsby, like mentioned above https://github.com/mui-org/material-ui/issues/12827#issuecomment-649595621) ?

@aphecetche Yes, it's something I have been eager to explore. I think that setting all the colors at a single place will help us increase consistency in the components (make it more obvious when we introduce a color that we don't really need)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

finaiized picture finaiized  路  3Comments

sys13 picture sys13  路  3Comments

iamzhouyi picture iamzhouyi  路  3Comments

FranBran picture FranBran  路  3Comments

ryanflorence picture ryanflorence  路  3Comments