Joy provides default style for each variant.
Default context overrides feature
Strapi defines 7 palettes (primary, neutral, danger, success, warning, secondary, and alternate). However, leave the secondary and alternate blank for now.
This example only focus on the light mode because the Figma does not provide the UIs for dark mode. See design tokens in Figma
declare module '@mui/joy/styles' {
// Joy has no '0', '150' token by default
// In the Figma, Strapi defines 0, 150 only for the neutral palette
interface PaletteNeutral {
150: string;
0: string;
}
}
<CssVarsProvider
prefix="strapi"
theme={extendTheme({
colorSchemes: {
light: {
palette: {
primary: {
700: '#271FE0',
600: '#4945FF',
500: '#7B79FF',
200: '#D9D8FF',
100: '#F0F0FF',
},
success: {
700: '#2F6846',
600: '#328048',
500: '#5CB176',
200: '#C6F0C2',
100: '#EAFBE7',
},
danger: {
700: '#B72B1A',
600: '#D02B20',
500: '#EE5E52',
200: '#F5C0B8',
100: '#FCECEA',
},
warning: {
700: '#BE5D01',
600: '#D9822F',
500: '#F29D41',
200: '#FAE7B9',
100: '#FDF4DC',
},
neutral: {
900: '#212134',
800: '#32324D',
700: '#4A4A6A',
600: '#666687',
500: '#8E8EA9',
400: '#A5A5BA',
300: '#C0C0CF',
200: '#DCDCE4',
150: '#EAEAEF',
100: '#F6F6F9',
0: '#FFFFFF',
},
},
},
},
})}
/>
Strapi does not define variant, so I have to translate the design myself to map with Joy variant.
From what I see, Strapi defines 2 styles for the Button, `solid` and `outlined`. Here is how to customize the variant token for each palette. Note that disabled state is the same across colors, so it is best to configure under theme.components.MuiButton
declare module '@mui/joy/styles' {
// other augmentation
interface Palette {
outlinedFocusBorder: string;
}
}
<CssVarsProvider
prefix="strapi"
theme={extendTheme({
colorSchemes: {
light: {
palette: {
primary: {
// ...tokens
solidHoverBg: 'var(--strapi-palette-primary-500)',
solidActiveBg: 'var(--strapi-palette-primary-700)',
outlinedColor: 'var(--strapi-palette-primary-600)',
outlinedBorder: 'var(--strapi-palette-primary-200)',
outlinedBg: 'var(--strapi-palette-primary-100)',
outlinedHoverBorder: 'var(--strapi-palette-primary-200)',
outlinedHoverBg: 'var(--strapi-palette-neutral-0)',
outlinedActiveColor: 'var(--strapi-palette-primary-700)',
outlinedActiveBorder: 'var(--strapi-palette-primary-700)',
outlinedActiveBg: 'var(--strapi-palette-neutral-0)',
},
success: {
// ...tokens
solidHoverBg: 'var(--strapi-palette-success-500)',
solidActiveBg: 'var(--strapi-palette-success-700)',
outlinedColor: 'var(--strapi-palette-success-600)',
outlinedBorder: 'var(--strapi-palette-success-200)',
outlinedBg: 'var(--strapi-palette-success-100)',
outlinedHoverBorder: 'var(--strapi-palette-success-200)',
outlinedHoverBg: 'var(--strapi-palette-neutral-0)',
outlinedActiveColor: 'var(--strapi-palette-success-700)',
outlinedActiveBorder: 'var(--strapi-palette-success-700)',
outlinedActiveBg: 'var(--strapi-palette-neutral-0)',
},
danger: {
// ...tokens
solidHoverBg: 'var(--strapi-palette-danger-500)',
solidActiveBg: 'var(--strapi-palette-danger-700)',
outlinedColor: 'var(--strapi-palette-danger-600)',
outlinedBorder: 'var(--strapi-palette-danger-200)',
outlinedBg: 'var(--strapi-palette-danger-100)',
outlinedHoverBorder: 'var(--strapi-palette-danger-200)',
outlinedHoverBg: 'var(--strapi-palette-neutral-0)',
outlinedActiveColor: 'var(--strapi-palette-danger-700)',
outlinedActiveBorder: 'var(--strapi-palette-danger-700)',
outlinedActiveBg: 'var(--strapi-palette-neutral-0)',
},
neutral: {
// ...tokens
outlinedColor: 'var(--strapi-palette-neutral-800)',
outlinedBorder: 'var(--strapi-palette-neutral-200)',
outlinedHoverBg: 'var(--strapi-palette-neutral-100)',
outlinedActiveBg: 'var(--strapi-palette-neutral-150)',
outlinedDisabledColor: 'var(--strapi-palette-neutral-600)',
outlinedDisabledBorder: 'var(--strapi-palette-neutral-200)',
outlinedDisabledBg: 'var(--strapi-palette-neutral-150)',
},
// these colors are not defined in the Figma button component, so leave them for now.
warning: {},
secondary: {},
alternate: {},
outlinedFocusBorder: 'var(--strapi-palette-neutral-0)',
},
},
},
MuiButton: {
styleOverrides: {
root: ({ ownerState }) => ({
'--Button-paddingInline': '1rem',
borderRadius: '4px',
...(ownerState.size === 'sm' && {
minHeight: 32,
}),
...(ownerState.size === 'md' && {
minHeight: 36,
}),
...(ownerState.size === 'lg' && {
minHeight: 40,
}),
'&.Mui-focusVisible': {
...(ownerState.variant === 'outlined' && {
borderColor: 'var(--strapi-palette-outlinedFocusBorder)',
}),
},
'&.Mui-disabled': {
backgroundColor: 'var(--strapi-palette-neutral-outlinedDisabledBg)',
color: 'var(--strapi-palette-neutral-outlinedDisabledColor)',
border: '1px solid',
borderColor: 'var(--strapi-palette-neutral-outlinedDisabledBorder)',
},
}),
},
},
})}
/>
Developer does not need to deal with the variant style sheet (eg. border, background-color, :hover, :active) because Joy will create those styles based on the final variables which means developers can also remove the default tokens by providing undefined.
Even though, Strapi does not have context override concept, it still work out-of-the box.
Strapi defines different focus style from Joy. This can be easily configure in theme.focus.default to apply to all focusable components.
<CssVarsProvider
prefix="strapi"
theme={extendTheme({
colorSchemes: {
// ...
},
focus: {
default: {
outline: '2px solid',
outlineOffset: '2px',
outlineColor: 'var(--strapi-palette-primary-700)',
},
},
})}
/>
The above UI looks exactly like the solid variant on top of soft variant. In this case, developers should extend only soft & solid variants to support secondary & alternate colors. (Joy does not provide secondary & alternate colors by default)
declare module '@mui/joy/styles' {
// other augmentation
interface VariantLight {
secondary: CSSObject;
alternate: CSSObject;
}
interface VariantContained {
secondary: CSSObject;
alternate: CSSObject;
}
}
<CssVarsProvider
prefix="strapi"
theme={extendTheme({
colorSchemes: {
light: {
palette: {
// ...other palettes
secondary: {
700: '#006096',
600: '#0C75AF',
500: '#66B7F1',
200: '#B8E1FF',
100: '#EAF5FF',
// Joy can detect the variables and will automatically generate variant styles
// even though the color does not exist in the default theme.
softBg: 'var(--strapi-palette-secondary-100)',
softColor: 'var(--strapi-palette-secondary-700)',
solidBg: 'var(--strapi-palette-secondary-500)',
solidColor: '#fff',
},
alternate: {
700: '#8312D1',
600: '#9736E8',
500: '#AC73E6',
200: '#E0C1F4',
100: '#F6ECFC',
// Joy can detect the variables and will automatically generate variant styles
// even though the color does not exist in the default theme.
softBg: 'var(--strapi-palette-alternate-100)',
softColor: 'var(--strapi-palette-alternate-700)',
solidBg: 'var(--strapi-palette-alternate-500)',
solidColor: '#fff',
},
}
}
},
})}
/>
// Custom component for Strapi use-case.
const Tile = ({
children,
variant = 'soft',
color = 'primary',
sx = [],
...props
}: {
variant?: 'soft' | 'solid';
color?: 'primary' | 'warning' | 'secondary' | 'alternate';
} & Omit<BoxProps, 'color'>) => {
return (
<Box
sx={[
{ display: 'inline-flex', p: 0.75, borderRadius: '4px' },
(theme) => theme.variants[variant][color], // easy
...(Array.isArray(sx) ? sx : [sx]),
]}
{...props}
>
{children}
</Box>
);
};
<Tile sx={{ p: 2 }}>
<Tile variant="solid">
<Info />
</Tile>
</Tile>
<Tile sx={{ p: 2 }} color="warning">
<Tile variant="solid" color="warning">
<Code />
</Tile>
</Tile>
<Tile sx={{ p: 2 }} color="secondary">
<Tile variant="solid" color="secondary">
<PlayArrow />
</Tile>
</Tile>
<Tile sx={{ p: 2 }} color="alternate">
<Tile variant="solid" color="alternate">
<HistoryEdu />
</Tile>
</Tile>