Skip to content
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

Support CSS variables as theme option #12827

Closed
abhisheksarka opened this issue Sep 10, 2018 · 47 comments
Closed

Support CSS variables as theme option #12827

abhisheksarka opened this issue Sep 10, 2018 · 47 comments
Assignees
Labels
breaking change new feature New feature or request package: system Specific to @mui/system priority: important This change can make a difference

Comments

@abhisheksarka
Copy link

abhisheksarka commented Sep 10, 2018

Supporting CSS variables would:

Benchmark

@oliviertassinari
Copy link
Member

oliviertassinari commented Sep 10, 2018

@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.

@rlacorne
Copy link

rlacorne commented Mar 17, 2019

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.

@mbrookes
Copy link
Member

@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.

@rlacorne
Copy link

rlacorne commented Mar 18, 2019

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.

@a-x-
Copy link
Contributor

a-x- commented Jul 26, 2019

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
// ...

@a-x-
Copy link
Contributor

a-x- commented Jul 26, 2019

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

@a-x-
Copy link
Contributor

a-x- commented Jul 26, 2019

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;
}

@a-x-
Copy link
Contributor

a-x- commented Jul 26, 2019

for one #12827 (comment) 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

@mh-alahdadian
Copy link

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

@mbrookes
Copy link
Member

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)

@HiranmayaGundu
Copy link

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.

@oliviertassinari
Copy link
Member

@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 oliviertassinari added the new feature New feature or request label May 29, 2020
@HiranmayaGundu
Copy link

@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)

@oliviertassinari oliviertassinari added this to the v5 milestone May 29, 2020
@oliviertassinari
Copy link
Member

oliviertassinari commented May 29, 2020

@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).

@balazsorban44
Copy link
Contributor

balazsorban44 commented Jun 25, 2020

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.)

@oliviertassinari
Copy link
Member

oliviertassinari commented Jun 25, 2020

@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.

@daveteu

This comment has been minimized.

@not-only-code
Copy link

not-only-code commented Jul 31, 2020

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.

@mbrookes
Copy link
Member

mbrookes commented Jul 31, 2020

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

@oliviertassinari
Copy link
Member

oliviertassinari commented May 9, 2021

Moving it to most v5 stable as it's unlikely it requires breaking changes.

@oliviertassinari oliviertassinari added package: system Specific to @mui/system and removed design This is about UI or UX design, please involve a designer labels Jul 20, 2021
@httpete
Copy link

httpete commented Oct 29, 2021

Moving it to v5.1 as it's unlikely it requires breaking changes.

Anxiously waiting for this. I have hacked it in for now, but the ability to leverage css --vars as theme variables is huge.

@httpete
Copy link

httpete commented Nov 9, 2021

Moving it to v5.1 as it's unlikely it requires breaking changes.

@oliviertassinari - 5.1 is out - and this didn't make it in. Any idea when?

@oliviertassinari
Copy link
Member

oliviertassinari commented Nov 10, 2021

@httpete Great point. I'm rephrasing all of this to avoid future confusion, there are no v5.1 plans. cc @mui-org/core. I'm closing the v5.1 milestone.

To expand a bit more on how we prioritize/plan. It's mostly on a quarterly basis for mid-term alignments and high-level outcomes, and low-alignment for short-term outcomes.

For instance, for Q4 2021, 40% of our time is 1. clean up all the stuff we broke with v5, 2. push unstyled components, 3. push a new design aesthetic with Joy. We have a specific measurable outcome we want to reach 3 months later down the road, and every few months, checking up where we are. We don't clearly define the steps as a team, it's mostly the individual owning the outcome that is accountable for organizing itself.
The other 40% of the time is the usual KPIs: number of open PRs, number of unanswered GitHub issues, npm downloads, docs traffic, etc. We are not working with specific release targets or even sprints like you could have in VSCode (they prepare what we come in 4 weeks before microsoft/vscode#136630). It's up to each member, based on what they perceive as having the highest priority, to work on.

On MUI X, the work is more structured, you take the same as above and add constraints. We define what problem we prioritize for the next 2 weeks in sprints, and how time will be spent during the quarter on all the high-level items. But even then, we don't work with specific release versions. We ship whatever we have on HEAD, following semver.

@oliviertassinari oliviertassinari removed this from the v5.1 milestone Nov 10, 2021
@RemyMachado
Copy link

RemyMachado commented Dec 27, 2021

I think that "supporting css properties as theme option" isn't a great idea, because theme switching wouldn't work. However, the other way around (supporting theme variables as css properties) is in my opinion the key to the solution.

Using this comment #12827 (comment)
I came up with an "OK tier" workaround implementing the following two mandatory points:

  • dynamic theme switching
  • single source of truth (no duplicate css properties definition). Everything is defined in the MUI theme (JS-side).

I'm using the GlobalStyles component to dynamicly inject the theme.

const setGlobalStyles = (theme: Theme) => (
  <GlobalStyles
    styles={{
      ':root': {
        '--color-primary': theme.palette.primary.main,
        '--color-secondary': theme.palette.secondary.main,
      },
    }}
  />
)

Here's a working demonstration. I synchronize it with the ThemeProvider as follows:

// import lightTheme & darkTheme

const MyApp = () => {
  const [theme, setTheme] = useState<Theme>(lightTheme)

  return (
      <ThemeProvider theme={theme}>
        {setGlobalStyles(theme)}
        <Button onClick={() => setTheme(darkTheme)}>dark theme</Button>
      </ThemeProvider>
  )
}

export default MyApp

I'm using one more ugly trick to tell my IDE that I defined these css properties:

/* globals.css */
.definitions {
    --color-primary: unset;
    --color-secondary: unset;
}

Note that I didn't check yet if it causes flickering with SSR implementations.

@mbrookes
Copy link
Member

@siriwatknp feel free to unassign yourself if not relevant to your current focus.

@siriwatknp
Copy link
Member

@mbrookes Thanks, this is now on my radar. I will create a proper RFC about adopting CSS variables for mui-material soon.

@RemyMachado I think that's a good quick workaround, however, you will still see a flicker when switching between light & dark mode. We have done some POC about how to fix it #27651 and I will create a plan for adding CSS variables to mui-material soon.

@denisovan31415
Copy link

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 #12827 (comment)) ?

Has the above feature been implemented to the latest release?

@siriwatknp
Copy link
Member

siriwatknp commented Jan 6, 2022

@junlueZH Not yet (but we have created a proposal) and it will be slightly different.

I have created a plan for adopting the CSS variables as a feature for @mui/material, please take look at #30485 and comments there.

cc @abhisheksarka @balazsorban44 @aphecetche @RemyMachado

@vdpdev
Copy link

vdpdev commented Jan 10, 2022

@siriwatknp The main idea of css variables for me is to use them inside of *.module.css files. This allows me to decrease rerenders because of hook styles changes (maybe they never will but it's still possible) and improve performance.
Does your proposal allows to use css variables inside of *.module.css files as below?
.title { marginTop: calc(var(--themeSpacing) * 2); }

@siriwatknp
Copy link
Member

siriwatknp commented Jan 11, 2022

Does your proposal allows to use CSS variables inside of *.module.css files as below?

@vdpdev Theoretically, I believe that it should work (both client & server-side rendering) because the CSS variables generated from the theme will be attached at the top of the HTML (:root), so any styling solution can use those variables.

However, I am not sure about how to make the development experience better (variable suggestion) for the styling solution like CSS modules, SCSS. But I think this is another topic for improvement.

@httpete
Copy link

httpete commented Jan 14, 2022

@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.

I tried to use the var syntax in the Theme:

    palette: {
      primary: {
        main: 'var(--my-backgroundColor-brandPrimary)',
      },
    },

and TableRow started to bark. If I use a js function to get the value from the global css root and pass that as a #hex it worked.

@siriwatknp
Copy link
Member

Hey everyone, we have the experimental APIs to generate the theme tokens to CSS variables. Please have a look at https://mui.com/material-ui/experimental-api/css-theme-variables/overview/.

I'm closing this issue!

@Falven
Copy link

Falven commented Mar 26, 2023

@siriwatknp Any reason this functionality doesn't generate the spacing, transition, etc. variables?

@siriwatknp
Copy link
Member

@siriwatknp Any reason this functionality doesn't generate the spacing, transition, etc. variables?

That'd be the next improvements.

@Falven
Copy link

Falven commented Mar 27, 2023

@siriwatknp thanks. I found the shouldSkipGeneratingVar function and override it to get some more properties generated. One problem I found is that if you're trying to override the primary and secondary colors from the variables with your own colors the elements will flash with the default color before applying yours.

@siriwatknp
Copy link
Member

siriwatknp commented Mar 27, 2023

@siriwatknp thanks. I found the shouldSkipGeneratingVar function and override it to get some more properties generated. One problem I found is that if you're trying to override the primary and secondary colors from the variables with your own colors the elements will flash with the default color before applying yours.

Please create a new issue and provide a CodeSandbox, I can take a look at it.

@peterhirn
Copy link

peterhirn commented Oct 11, 2023

Setting primary/secondary palette colors via var(--xyz) still doesn't work and there is no mention of overriding these colors in the official documentation.

Using getComputedStyle to set the colors seems to work, but then requires more code to honor dark/light mode toggle.

Anyone got a good solution for this? 🙏

ps. telling users that the html will be larger and therefore the FCP slower is moot when everyone is using brotli/gzip anyways and the size difference is very likely insignificant.

@siriwatknp
Copy link
Member

var(--xyz)

Can you create a new issue and give some more detail about it? thanks a lot.

@peterhirn
Copy link

Following these instructions https://mui.com/material-ui/experimental-api/css-theme-variables/customization/#color-schemes

  const theme = extendTheme({
    colorSchemes: {
      light: {
        palette: {
          primary: {
            main: "var(--my-color)"
          }
        }
      },
      dark: {
        palette: {
          primary: {
            main: "var(--my-color)"
          }
        }
      }
    }
  })

produces the following error

MUI: Unsupported `var(--my-color)` color.
The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().

Wrapping the values in rgb or rgba doesn't seem to work, whereas using a hex literal like #ff0000 works.

@DGaibor
Copy link

DGaibor commented Feb 7, 2024

Following these instructions https://mui.com/material-ui/experimental-api/css-theme-variables/customization/#color-schemes

  const theme = extendTheme({
    colorSchemes: {
      light: {
        palette: {
          primary: {
            main: "var(--my-color)"
          }
        }
      },
      dark: {
        palette: {
          primary: {
            main: "var(--my-color)"
          }
        }
      }
    }
  })

produces the following error

MUI: Unsupported `var(--my-color)` color.
The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().

Wrapping the values in rgb or rgba doesn't seem to work, whereas using a hex literal like #ff0000 works.

I have the same issue.
I have this:

palette:{
        primary: {
            light: 'var(--foreground-first-light)',
            main: 'var(--foreground-first)',
            dark: 'var(--foreground-first-dark)',
            contrastText: 'var(--foreground-first-contrastText)',
        },
}

and when I use I get an error

Unhandled Runtime Error
Error: MUI: Unsupported `var(--foreground-first)` color.
The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change new feature New feature or request package: system Specific to @mui/system priority: important This change can make a difference
Projects
None yet
Development

No branches or pull requests