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
anddark
. - 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"><</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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"></</span>h1</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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"></</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"></</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"><</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"><</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"></</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
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 applicationmode
(mode
by default)colorSchemeStorageKey?
: localStorage key used to storecolorScheme
defaultColorScheme
: Design system default color scheme (string or object depending on if the design system has 1 or more themes, can belight
ordark
)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 bycreateCssVarsProvider
, 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 toThemeProvider
.
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 modesenableColorScheme: boolean
- Indicate to the browser which color scheme is used (light or dark) for rendering built-in UIprefix: string
- CSS variable prefixtheme: ThemeInput
- the theme provided to React's contextmodeStorageKey?: string
- localStorage key used to store applicationmode
attribute?: string
- DOM attribute for applying color scheme
useColorScheme: () => ColorSchemeContextValue
mode: string
- The user's selected modesetMode: mode => {…}
- Function for setting themode
. Themode
is saved to internal state and local storage; ifmode
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 applicationmode
attribute?: string
- DOM attribute for applying color scheme