Skip to content

CSS theme variables

An overview of adopting CSS theme variables in Material UI or Joy UI.

CSS variables are a modern cross-browser feature that let you declare variables in CSS and reuse them in other properties.

Introduction

CSS theme variable support is a new feature in MUI System added in v5.0.5 as an experimental export. It tells the underlying Material UI, Joy UI or even custom UI library components to use the generated CSS theme variables instead of raw values. This provides significant improvements in developer experience related to theming and customization. With these variables, you can inject a theme into your app's stylesheet at build time to apply the user's selected settings before the whole app is rendered. You can checkout the advantages and trade-offs of using CSS theme variables before using them.

Advantages

  • It lets you prevent dark-mode SSR flickering.
  • You can create unlimited color schemes beyond light and dark.
  • It offers a better debugging experience not only for developers but also designers on your team.
  • The color scheme of your website is automatically synced between browser tabs.
  • It simplifies integration with third-party tools because CSS theme variables are available globally.
  • It reduces the need for a nested theme when you want to apply dark styles to a specific part of your application.

Trade-offs

For server-side applications, there are some trade-offs to consider:

Compare to the default method Reason
HTML size Bigger CSS variables are generated for both light and dark mode at build time.
First Contentful Paint (FCP) Larger Since the HTML size is generally bigger, the time to download the HTML before showing the content is longer.
Time to Interactive (TTI) Smaller (for dark mode) Stylesheets are not regenerated between light and dark mode, so it takes less time for JavaScript to run.

Usage

The CSS variables API usage is exposed as a higher order function called unstable_createCssVarsProvider which can be called to create a theme provider and other utitlities to share the theme config throughout your app. This is a very low-level function and has a lot of moving parts. If you are already using Material UI or Joy UI, they already expose their own CssVarsProvider component that you can use directly without needing to configure your theme. Now that's out of the way, we can continue with how this util can be used.

We'll first define a minimal theme palette for light and dark modes.

<span class="token comment">// extendTheme.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  unstable_createGetCssVar <span class="token keyword">as</span> systemCreateGetCssVar<span class="token punctuation">,</span>
  unstable_prepareCssVars <span class="token keyword">as</span> prepareCssVars<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@mui/system'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> lightColorScheme <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">palette</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">'light'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">primary</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">default</span><span class="token operator">:</span> <span class="token string">'#3990FF'</span><span class="token punctuation">,</span>
      <span class="token literal-property property">dark</span><span class="token operator">:</span> <span class="token string">'#02367D'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">default</span><span class="token operator">:</span> <span class="token string">'#111111'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// ... other colors</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> darkColorScheme <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">palette</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">'dark'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">primary</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">default</span><span class="token operator">:</span> <span class="token string">'#265D97'</span><span class="token punctuation">,</span>
      <span class="token literal-property property">dark</span><span class="token operator">:</span> <span class="token string">'#132F4C'</span><span class="token punctuation">,</span>
      <span class="token literal-property property">main</span><span class="token operator">:</span> <span class="token string">'#5090D3'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token literal-property property">text</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token keyword">default</span><span class="token operator">:</span> <span class="token string">'#ffffff'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// ... other colors</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> createGetCssVar <span class="token operator">=</span> <span class="token punctuation">(</span>cssVarPrefix <span class="token operator">=</span> <span class="token string">'my-app'</span><span class="token punctuation">)</span> <span class="token operator">=></span>
  <span class="token function">systemCreateGetCssVar</span><span class="token punctuation">(</span>cssVarPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">extendTheme</span><span class="token punctuation">(</span><span class="token punctuation">{</span> cssVarPrefix <span class="token operator">=</span> <span class="token string">'my-app'</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> getCssVar <span class="token operator">=</span> <span class="token function">createGetCssVar</span><span class="token punctuation">(</span>cssVarPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> theme <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">colorSchemes</span><span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token literal-property property">light</span><span class="token operator">:</span> lightColorScheme<span class="token punctuation">,</span>
      <span class="token literal-property property">dark</span><span class="token operator">:</span> darkColorScheme<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// ... any other objects independent of color-scheme,</span>
    <span class="token comment">// like fontSizes, spacing tokens, etc</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token literal-property property">vars</span><span class="token operator">:</span> themeVars<span class="token punctuation">,</span> generateCssVars <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">prepareCssVars</span><span class="token punctuation">(</span>
    <span class="token punctuation">{</span> <span class="token literal-property property">colorSchemes</span><span class="token operator">:</span> theme<span class="token punctuation">.</span>colorSchemes <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token literal-property property">prefix</span><span class="token operator">:</span> cssVarPrefix<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
  theme<span class="token punctuation">.</span>vars <span class="token operator">=</span> themeVars<span class="token punctuation">;</span>
  theme<span class="token punctuation">.</span>generateCssVars <span class="token operator">=</span> generateCssVars<span class="token punctuation">;</span>
  theme<span class="token punctuation">.</span>palette <span class="token operator">=</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>theme<span class="token punctuation">.</span>colorSchemes<span class="token punctuation">.</span>light<span class="token punctuation">.</span>palette<span class="token punctuation">,</span>
    <span class="token literal-property property">colorScheme</span><span class="token operator">:</span> <span class="token string">'light'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> theme<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> myCustomDefaultTheme <span class="token operator">=</span> <span class="token function">extendTheme</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> myCustomDefaultTheme<span class="token punctuation">;</span>

Here, the returned theme object needs to follow a certain structure to be used correctly by the final CssVarsProvider. It should have a colorSchemes key with the light and dark (and any other) palette. prepareCssVars import from @mui/system is used to create css variable names which can then be easily accessed using the returned vars. This is also added to the theme object. Finally, myCustomDefaultTheme theme object is created that can now be passed to the createCssVarsProvider to get a CssVarsProvider.

<span class="token comment">// CssVarsProvider.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> unstable_createCssVarsProvider <span class="token keyword">as</span> createCssVarsProvider <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@mui/system'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token punctuation">{</span> CssVarsProvider<span class="token punctuation">,</span> useColorScheme <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">createCssVarsProvider</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">defaultColorScheme</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token literal-property property">light</span><span class="token operator">:</span> <span class="token string">'light'</span><span class="token punctuation">,</span>
    <span class="token literal-property property">dark</span><span class="token operator">:</span> <span class="token string">'dark'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">theme</span><span class="token operator">:</span> myCustomDefaultTheme<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token punctuation">{</span> CssVarsProvider<span class="token punctuation">,</span> useColorScheme <span class="token punctuation">}</span><span class="token punctuation">;</span>

Now wrap your top level app component with this CssVarsProvider component and then you can access the passed theme value to any of the components rendered inside the provider.

Example of a component using the css variable -

<span class="token comment">// Button.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> styled <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@mui/system'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> Button <span class="token operator">=</span> <span class="token function">styled</span><span class="token punctuation">(</span><span class="token string">'button'</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> theme <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">backgroundColor</span><span class="token operator">:</span> theme<span class="token punctuation">.</span>vars<span class="token punctuation">.</span>palette<span class="token punctuation">.</span>primary<span class="token punctuation">.</span>default<span class="token punctuation">,</span>
  <span class="token literal-property property">border</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1px solid </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>theme<span class="token punctuation">.</span>vars<span class="token punctuation">.</span>palette<span class="token punctuation">.</span>primary<span class="token punctuation">.</span>dark<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
  <span class="token literal-property property">color</span><span class="token operator">:</span> theme<span class="token punctuation">.</span>vars<span class="token punctuation">.</span>palette<span class="token punctuation">.</span>text<span class="token punctuation">.</span>default<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> Button<span class="token punctuation">;</span>

The hook, useColorScheme can be used to get the current mode (light or dark) and can also update the mode like:

<span class="token comment">// App.js</span>
<span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> setMode<span class="token punctuation">,</span> mode <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">useColorScheme</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token function-variable function">toggleMode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token function">setMode</span><span class="token punctuation">(</span>mode <span class="token operator">===</span> <span class="token string">'dark'</span> <span class="token operator">?</span> <span class="token string">'light'</span> <span class="token operator">:</span> <span class="token string">'dark'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Current Mode<span class="token operator">:</span> <span class="token punctuation">{</span>mode<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>toggleMode<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Toggle Mode<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">Button</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// main.js</span>
<span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> ReactDOM <span class="token keyword">from</span> <span class="token string">'react-dom/client'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> App <span class="token keyword">from</span> <span class="token string">'./App'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CssVarsProvider <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./CssVarsProvider'</span><span class="token punctuation">;</span>

ReactDOM<span class="token punctuation">.</span><span class="token function">createRoot</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">CssVarsProvider</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">App</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">CssVarsProvider</span></span><span class="token punctuation">></span></span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

Now, the Button's backgroundColor, borderColor and text color values will correctly use the colors based on the selected mode.

Demo

Press Enter to start editing

For framework or language specific setup, see this

See the complete usage of createVssVarsProvider in Material UI and Joy UI.

API

createCssVarsProvider options

  • attribute?: DOM attribute for applying color scheme (data-color-scheme by default)
  • modeStorageKey?: localStorage key used to store application mode (mode by default)
  • colorSchemeStorageKey?: localStorage key used to store colorScheme
  • defaultColorScheme: Design system default color scheme (string or object depending on if the design system has 1 or more themes, can be light or dark)
  • defaultMode?: Design system default mode (light by default)
  • disableTransitionOnChange?: Disable CSS transitions when switching between modes or color schemes (false by default)
  • themeId?: The design system's unique id for getting the corresponded theme when there are multiple design systems.
  • theme: Design system default theme. It's structure, besides the minimium requirements by createCssVarsProvider, is upto the design system to implement.
  • resolveTheme(theme: Theme) => Theme: A function to be called after the CSS variables are attached. The result of this function will be the final theme pass to ThemeProvider.

createCssVarsProvider returns 3 items.

<CssVarsProvider> props

  • defaultMode?: 'light' | 'dark' | 'system' - Application's default mode (light by default)
  • disableTransitionOnChange : boolean - Disable CSS transitions when switching between modes
  • enableColorScheme: boolean - Indicate to the browser which color scheme is used (light or dark) for rendering built-in UI
  • prefix: string - CSS variable prefix
  • theme: ThemeInput - the theme provided to React's context
  • modeStorageKey?: string - localStorage key used to store application mode
  • attribute?: string - DOM attribute for applying color scheme

useColorScheme: () => ColorSchemeContextValue

  • mode: string - The user's selected mode
  • setMode: mode => {…} - Function for setting the mode. The mode is saved to internal state and local storage; if mode is null, it will be reset to the default mode

getInitColorSchemeScript: (options) => React.ReactElement

options

  • defaultMode?: 'light' | 'dark' | 'system': - Application's default mode before React renders the tree (light by default)
  • modeStorageKey?: string: - localStorage key used to store application mode
  • attribute?: string - DOM attribute for applying color scheme