Theme tokens
Learn about the two categories of tokens within Joy UI's default theme and how to customize them.
The W3C Community Group defines design tokens as: "...indivisible pieces of a design system such as colors, spacing, typography scale." Joy UI builds up on this concept to develop its theme, consisting of two categories: low-level and global variant tokens.
Low-level tokens
Low-level tokens refer to the smallest units of style that defines the look and feel Joy UI has out-of-the-box. They're labeled as low-level because they can be used to compose larger tokens, such as the typography scale.
Structure
Joy UI's default theme has three main categories of low-level design tokens:
- Color
- Typography
- Shape-related
Color
The first theme node within the color category is colorSchemes
.
It houses the light
and dark
nodes, and inside each one of them, there is a palette
node, containing the global variant tokens adjusted for both modes.
colorSchemes: {
light: {
palette: {
primary: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
},
neutral: {...},
...
},
},
dark: {
palette: {
primary: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
},
neutral: {...},
...
},
},
}
You can check the ColorSystem interface to see all of the available interfaces.
Channel tokens
The tokens ended with Channel
are automatically generated from the provided theme unless you explicitly specify them.
These tokens are useful for creating translucent (alpha) colors.
import Typography from '@mui/joy/Typography';
<Typography
sx={theme => ({
color: `rgba(${theme.vars.palette.primary.mainChannel} / 0.72)`,
})}
>
Typography
Within the typography-related tokens, there are first the ones that map out to common CSS typography properties:
fontSize: {...},
fontFamily: {...},
fontWeight: {...},
lineHeight: {...},
letterSpacing: {...},
They're then used to build up Joy UI's typographic scale:
typography: {
h1: {
fontFamily: 'var(--joy-fontFamily-display)',
fontWeight: 'var(--joy-fontWeight-lg)' as CSSProperties['fontWeight'],
fontSize: 'var(--joy-fontSize-xl4)',
lineHeight: 'var(--joy-lineHeight-sm)',
letterSpacing: 'var(--joy-letterSpacing-sm)',
color: 'var(--joy-palette-text-primary)',
},
h2: {...},
h3: {...},
...
}
Shape
The two main theme nodes related to shape elements are:
radius: {...},
shadow: {...},
Overriding low-level tokens
To customize the theme's low-level design tokens, use the extendTheme
API to create a new theme and then pass it to the CssVarsProvider
.
The specified tokens will be deeply merged into the default values.
import { CssVarsProvider, extendTheme } from '@mui/joy/styles';
const theme = extendTheme({
colorSchemes: {
light: {
palette: {
background: {
// palette.neutral.50 is the default token
body: 'var(--joy-palette-neutral-50)',
},
},
},
},
});
function App() {
return <CssVarsProvider theme={theme}>...</CssVarsProvider>;
}
Adding low-level tokens
You can add any custom tokens to the theme and still be able to use them in APIs like styled
and sx
prop.
extendTheme({
colorSchemes: {
light: {
palette: {
// Example of new color tokens.
// We recommend to limit them to 3 levels deep-in this case `palette.brand.primary`.
brand: {
primary: 'green',
secondary: 'red',
},
},
},
},
});
For TypeScript, you need to augment the theme structure to include the new tokens.
import { CssVarsProvider, extendTheme } from '@mui/joy/styles';
declare module '@mui/joy/styles' {
interface Palette {
brand: {
primary: string;
secondary: string;
};
}
}
After that, you can use those tokens in the styled
function or the sx
prop:
// sx prop
<Button sx={{ color: 'brand.primary' }} />;
// styled function
const Text = styled('p')(({ theme }) => ({
color: theme.vars.palette.brand.primary,
}));
Global variant tokens
By default, Joy UI has four built-in global variants tokens: plain
, outlined
, soft
, and solid
.
To learn more about it, check the dedicated global variants page.
Structure
The colors for each variant are defined inside the palette
node.
The variant name is composed of three parts, in the format of variant type | state | CSS property.
For example:
solidBg
refers to the solid variant initial state (as there is none specified) background color.outlinedHoverBorder
refers to the outlined variant hovered border color.
// theme
{
colorSchemes: {
light: {
palette: {
primary: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
neutral: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
danger: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
info: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
success: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
warning: {
plainColor: 'valid CSS color',
plainHoverBg: 'valid CSS color',
plainActiveBg: 'valid CSS color',
// ...other variant tokens
},
}
},
dark: {
// ...same structure with different values
}
}
}
Overriding global variant tokens
If you want to customize the global variants, we recommend to start from the Button component as it tends to have the larger amount of interactive variants when compared to other components.
As an example, let's customize Joy's Button
so they look like the ones from Bootstrap:
- Bootstrap's default buttons are comparable to Joy's
solid
variant. - Bootstrap's
secondary
variant uses a grey color, similar to Joy'sneutral
. - Bootstrap's
btn-light
is similar to Joy's button using thesoft
variant andneutral
color palette. - Joy doesn't have anything similar, out-of-the-box, to Bootstrap's
btn-dark
.- We could achieve that using one of the tree main customization approaches.
Removing global variants tokens
To remove a global variant token, use undefined
as a value.
For example, all default global variant tokens comes with the active
state.
If you don't want to have the style for the :active
pseudo class you can do this:
import * as React from 'react';
import { CssVarsProvider, extendTheme } from '@mui/joy/styles';
import Box from '@mui/joy/Box';
import Button from '@mui/joy/Button';
// ⚠️ If the value is `undefined`, it should be `undefined` for other color schemes as well.
const theme = extendTheme({
colorSchemes: {
light: {
palette: {
primary: {
solidActiveBg: undefined,
},
},
},
dark: {
palette: {
primary: {
solidActiveBg: undefined,
},
},
},
},
});
const useEnhancedEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
export default function BootstrapVariantTokens() {
// the `node` is used for attaching CSS variables to this demo, you might not need it in your application.
const [node, setNode] = React.useState(null);
useEnhancedEffect(() => {
setNode(document.getElementById('remove-active-tokens-demo'));
}, []);
return (
<CssVarsProvider
theme={theme}
colorSchemeNode={node || null}
colorSchemeSelector="#remove-active-tokens-demo"
>
<Box
id="remove-active-tokens-demo"
sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}
>
<Button>Button</Button>
</Box>
</CssVarsProvider>
);
}
Custom global variant token styles
You can apply custom styles to each global variant via the variants
node.
They can also be applied to a specific palette, which will therefore be merged to the styles generated from the global variant tokens.
The shadow applied
No custom shadow
import * as React from 'react';
import { CssVarsProvider, extendTheme } from '@mui/joy/styles';
import Box from '@mui/joy/Box';
import Button from '@mui/joy/Button';
import Typography from '@mui/joy/Typography';
const theme = extendTheme({
variants: {
solid: {
primary: {
boxShadow: '0 2px 6px 0 rgba(0,0,0,0.3)',
},
},
solidHover: {
primary: {
'&:hover': {
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.4)',
},
},
},
},
});
const useEnhancedEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
export default function BootstrapVariantTokens() {
// the `node` is used for attaching CSS variables to this demo, you might not need it in your application.
const [node, setNode] = React.useState(null);
useEnhancedEffect(() => {
setNode(document.getElementById('custom-variant-style-demo'));
}, []);
return (
<CssVarsProvider
theme={theme}
colorSchemeNode={node || null}
colorSchemeSelector="#custom-variant-style-demo"
>
<Box
id="custom-variant-style-demo"
sx={{
display: 'flex',
gap: 4,
flexWrap: 'wrap',
'& > div': { textAlign: 'center' },
}}
>
<Box>
<Button>Button</Button>
<Typography level="body3" my={1}>
The shadow applied
</Typography>
</Box>
<Box>
<Button color="danger">Button</Button>
<Typography level="body3" my={1}>
No custom shadow
</Typography>
</Box>
</Box>
</CssVarsProvider>
);
}