A zero-runtime CSS-in-JS library that extracts the colocated css to it's own css files at build-time.
Zero-runtime supports Next.js and Vite with future support for more bundlers—you must install the corresponding plugin, as shown below.
npm install @mui/zero-runtime
npm install --save-dev @mui/zero-next-plugin
Then, in your next.config.js
file, import the plugin and wrap the exported config object:
const { withZeroPlugin } = require('@mui/zero-next-plugin');
module.exports = withZeroPlugin({
// ... Your nextjs config.
});
npm install @mui/zero-runtime
npm install --save-dev @mui/zero-vite-plugin
Then, in your vite config file file, import the plugin and wrap the exported config object:
import { zeroVitePlugin } from '@mui/zero-vite-plugin';
export default defineConfig({
plugins: [
zeroVitePlugin(),
// ... Your other plugins.
],
});
Use the css
API to create reusable styles:
import { css } from '@mui/zero-runtime';
const visuallyHidden = css({
border: 0,
clip: 'rect(0 0 0 0)',
height: '1px',
margin: -1,
overflow: 'hidden',
padding: 0,
position: 'absolute',
whiteSpace: 'nowrap',
width: '1px',
});
function App() {
return <div className={visuallyHidden}>I am invisible</div>;
}
The call to css
function will be replaced with a unique string that represents the CSS class name for the generated styles.
Use a callback function to get access to the theme values:
const title = css(({ theme }) => ({
color: theme.colors.primary,
fontSize: theme.spacing.unit * 4,
fontFamily: theme.typography.fontFamily,
}));
Use the styled
API to create a component by passing styles at the end. The usage should be familiar if you've worked with Emotion or styled-components:
import { styled } from '@mui/zero-runtime';
const Heading = styled('div')({
fontSize: '4rem',
fontWeight: 'bold',
padding: '10px 0px',
});
function App() {
return <Heading>Hello</Heading>;
}
The zero-runtime package differs from "standard" runtime CSS-in-JS libraries in a few ways:
- You never get direct access to props in your styled declarations. This is because prop values are only available at runtime, but the CSS is extracted at build time. See Styling based on runtime values for a workaround.
- Your styles must be declarative, and must account for all combinations of props that you want to style.
- The theme lets you declare CSS tokens that become part of the CSS bundle after the build. Any other values and methods that it might have are only available during build time—not at runtime. This leads to smaller bundle sizes.
💡 This approach is recommended when the value of the prop is known at build time (finite values).
Use the variants
to define styles for a combination of the component's props.
Each variant is an object with props
and style
keys. The styles are applied when the component's props match the props
object.
Example 1: A button component with small
and large
sizes:
const Button = styled('button')({
border: 'none',
padding: '0.75rem',
// ...other base styles
variants: [
{
props: { size: 'large' },
style: { padding: '1rem' },
},
{
props: { size: 'small' },
style: { padding: '0.5rem' },
},
],
});
<Button>Normal button</Button>; // padding: 0.75rem
<Button size="large">Large button</Button>; // padding: 1rem
<Button size="small">Small button</Button>; // padding: 0.5rem
Example 2: A button component with variants and colors:
const Button = styled('button')({
border: 'none',
padding: '0.75rem',
// ...other base styles
variants: [
{
props: { variant: 'contained', color: 'primary' },
style: { backgroundColor: 'tomato', color: 'white' },
},
],
});
// `backgroundColor: 'tomato', color: 'white'`
<Button variant="contained" color="primary">
Submit
</Button>;
Example 3: Apply styles based on a condition:
The value of the props
can be a function that returns a boolean. If the function returns true
, the styles are applied.
const Button = styled('button')({
border: 'none',
padding: '0.75rem',
// ...other base styles
variants: [
{
props: (props) => props.variant !== 'contained',
style: { backgroundColor: 'transparent' },
},
],
});
💡 This approach is recommended when the value of a prop is unknown ahead of time or possibly unlimited values, e.g. styling based on the user's input.
Use a callback function as a value to create a dynamic style for the specific CSS property:
const Heading = styled('h1')({
color: ({ isError }) => (isError ? 'red' : 'black'),
});
Zero-runtime will replace the callback with a CSS variable and inject the value through inline style. This makes it possible to create a static CSS file while still allowing dynamic styles.
.Heading_class_akjsdfb {
color: var(--Heading_class_akjsdfb-0);
}
<h1
style={{
'--Heading_class_akjsdfb-0': ({ isError }) => (isError ? 'red' : 'black'),
}}
>
Hello
</h1>
All of the components that you create are also available as CSS selectors. For example, for the Heading
component described in the previous section, you can target that component inside another styled component like this:
const Wrapper = styled.div({
[`& .${Heading}`]: {
color: 'blue',
},
});
This enables you to override the default color
of the Heading component rendered inside the Wrapper:
<Wrapper>
<Heading>Hello</Heading>
</Wrapper>
You can also export any styled component you create and use it as the base for additional components:
const ExtraHeading = styled(Heading)({
// ... overridden styled
});
If you use TypeScript, add the props typing before the styles to get the type checking:
const Heading = styled('h1')<{ isError?: boolean }>({
color: ({ isError }) => (isError ? 'red' : 'black'),
});
Theming is an optional feature that lets you reuse the same values, such as colors, spacing, and typography, across your application. It is a plain object of any structure that you can define in your config file.
💡 Good to know:
The theme object are used at build time without relying on React context like common CSS-in-JS libraries. This means that components created by zero-runtime
styled
will be React Server Component by default and still get benefits from theming.
For example, in Next.js, you can define a theme in the next.config.js
file like this:
const { withZeroPlugin } = require('@mui/zero-next-plugin');
module.exports = withZeroPlugin(
{
// ...other nextConfig
},
{
theme: {
colors: {
primary: 'tomato',
secondary: 'cyan',
},
spacing: {
unit: 8,
},
typography: {
fontFamily: 'Inter, sans-serif',
},
// ...more keys and values, it's free style!
},
},
);
A callback can be used with styled and css APIs to access the theme values:
const Heading = styled('h1')(({ theme }) => ({
color: theme.colors.primary,
fontSize: theme.spacing.unit * 4,
fontFamily: theme.typography.fontFamily,
}));
Zero-runtime can generate CSS variables from the theme values when you wrap your theme with extendTheme
utility. For example, in a next.config.js
file:
const { withZeroPlugin, extendTheme } = require('@mui/zero-next-plugin');
module.exports = withZeroPlugin(
{
// ...other nextConfig
},
{
theme: extendTheme({
colors: {
primary: 'tomato',
secondary: 'cyan',
},
spacing: {
unit: 8,
},
typography: {
fontFamily: 'Inter, sans-serif',
},
}),
},
);
The extendTheme
utility will go through the theme and create a vars
object which represents the tokens that refer to CSS variables.
const theme = extendTheme({
colors: {
primary: 'tomato',
secondary: 'cyan',
},
});
console.log(theme.colors.primary); // 'tomato'
console.log(theme.vars.colors.primary); // 'var(--colors-primary)'
Some tokens, especially color-related tokens, can have different values for different scenarios. For example in a daylight condition, the background color might be white, but in a dark condition, it might be black.
The extendTheme
utility lets you define theme with a special colorSchemes
key:
extendTheme({
colorSchemes: {
light: {
colors: {
background: '#f9f9f9',
foreground: '#121212',
},
},
dark: {
colors: {
background: '#212121',
foreground: '#fff',
},
},
},
});
In the above example, light
(default) and dark
color schemes are defined. The structure of each color scheme must be a plain object with keys and values.
By default, when colorSchemes
is defined, zero-runtime uses the prefers-color-scheme
media query to switch between color schemes based on user's system settings.
However, if you want to control the color scheme based on application logic, for example, using a button to switch between light and dark mode, you can customize the behavior by providing a getSelector
function:
extendTheme({
colorSchemes: {
light: { ... },
dark: { ... },
},
+ getSelector: (colorScheme) => colorScheme ? `.theme-${colorScheme}` : ':root',
});
Note that you need to add a logic to a button by yourself. Here is an example of how to do it:
function App() {
return (
<button
onClick={() => {
document.documentElement.classList.toggle('theme-dark');
}}
>
Toggle color scheme
</button>
);
}
You can add a prefix to the generated CSS variables by providing a cssVarPrefix
option to the extendTheme
utility:
extendTheme({
cssVarPrefix: 'zero',
});
The generated CSS variables will have the zero
prefix:
:root {
--zero-colors-background: #f9f9f9;
--zero-colors-foreground: #121212;
}
To get the type checking for the theme, you need to augment the theme type:
// any file that is included in your tsconfig.json
import type { ExtendTheme } from '@mui/zero-runtime';
declare module '@mui/zero-runtime/theme' {
interface ThemeTokens {
// the structure of your theme
}
interface ThemeArgs {
theme: ExtendTheme<{
colorScheme: 'light' | 'dark';
tokens: ThemeTokens;
}>;
}
}