Material UI Guide
Material UI
Currently we are using the latest version of Material UI at the time of writing this doc (v5.8.5) It comes packaged with default styles, and is optimized to work with Emotion (or styled-components).
Warning: Using styled-components as an engine at this moment is not working when used in a SSR projects.Theme customization
The Material UI theme is a global feature that allows us to share styles and configure Material UI components globally. In this place we will define all the most important properties that the Material UI components will inherit and the components that we create from the tools that Material UI has for it.
Normally a Material UI theme has some of the following properties that can be overridden (You don't need to override everything, just the properties or sub properties you need to change)
- Theme Properties:
breakpointsspacingcomponentspalettetypographyshadowstransitionszIndex
In this guide we will focus on frequently used properties, you can learn more about all the material theme properties in its documentation https://mui.com/material-ui/customization/theming/
Breakpoints
The easiest way is to override the Material UI breakpoints:
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
},
},
});
But you can also create new nomenclatures:
const theme = createTheme({
breakpoints: {
values: {
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1200,
},
},
});
If you are using TypeScript, you would also need to use module augmentation:
declare module '@mui/material/styles' {
interface BreakpointOverrides {
xs: false; // removes the `xs` breakpoint
sm: false;
md: false;
lg: false;
xl: false;
mobile: true; // adds the `mobile` breakpoint
tablet: true;
laptop: true;
desktop: true;
}
}
Spacing
The theme.spacing() helps to create consistent spacing between the elements of your UI, you can use a function, a number or an array of values.
const theme = createTheme({
spacing: (factor) => `${0.25 * factor}rem`, // (Bootstrap strategy)
});
theme.spacing(2); // = 0.25 * 2rem = 0.5rem = 8px
1.3. Components
You can customize a component's styles, default props, and more by using its keys inside the theme. This helps to achieve styling consistency across your application.
Each component within the theme receives 3 properties defaultProps, styleOverrides and variants
defaultProps: You could define properties of the component, you can define own properties of the component, like the ripple animation in the buttons or inherited from the HTML elements.styleOverrides: You can override CSS properties of the Base component, it is recommended to customize some properties that are shared between the different variants of a component. Normally you will userootto define the styles, but in some cases it will be necessary to use other properties, as in the example below.variants: You can override or define new variants for the component, you just need to specify one or more properties that will be used to style the component when it uses them.
const theme = createTheme({
components: {
// Name of the component
MuiTooltip: {
defaultProps: {
// The props to change the default for.
disableRipple: true, // No more ripple, on the whole application 💣 (for this component)!
},
styleOverrides: {
root: {
// Some CSS
fontSize: '1rem',
},
tooltip: {
fontSize: '2rem'
}
}
variants: [
{
props: { variant: 'dashed', size: 'small' },
style: {
textTransform: 'none',
border: `2px dashed ${blue[500]}`,
},
},
]
},
},
});
// The element will be rendered with a blue dashed border of 2 pixels
// (only if we define the right properties when using the component)
<Tooltip variant="dashed" size="small" />
If you have your theme separated by files and use TypeScript, you can use the types like this:
theme/
├ index.js
└ components/
├ index.js
├ MuiDialog.component.js
└ MuiAlert.component.js
// theme/index.js
import themeComponents from './theme.components.js'
const theme = createTheme({
components: themeComponents
})
// theme/components/index.js
import MuiButtonOverride from './MuiButton.component.js'
export themeComponents: ThemeOptions["components"]: {
MuiButton: MuiButtonOverride
}
// theme/components/MuiDialog.component.js
export const MuiDialog: Components['MuiDialog'] = { ... }
Palette
The color palette will allow you to define the primary and secondary colors, errors/success/info/warning, text colors, etc. The most important thing in this section are the primary and secondary colors, since these colors will be inherited by the Material UI components and the components that we create new You can easily create a palette just by setting the main color of the primary or secondary palette, Material UI will calculate by proximity the light and dark colors
const theme = createTheme({
palette: {
primary: {
main: blue[500],
// light: will be calculated from palette.primary.main,
// dark: will be calculated from palette.primary.main,
},
secondary: {
main: pink[500],
},
error: {
main: red[200],
light: pink[100]
},
common: {
black: grey[900],
white: grey[100],
}
},
});
Here an example defining the colors of the palette, really useful when the colors change (for example a button would have the main color by default and the color dark when hovering, we could define red as main and green as dark)
const theme = createTheme({
palette: {
primary: {
main: blue[500],
light: yellow[300],
dark: red[800],
contrastText: grey[100]
},
},
});
The material color system is based on the Munsell color system, you can import material colors with this system or create your own
const grey = {
"50": "#F3F6F9", // from lightest
"100": "#E7EBF0",
"200": "#E0E3E7",
"300": "#CDD2D7",
"400": "#B2BAC2",
"500": "#A0AAB4",
"600": "#6F7E8C",
"700": "#3E5060",
"800": "#2D3843",
"900": "#1A2027", // to darkest
// complementary
"A100": "#f5f5f5", // from lightest
"A200": "#eeeeee",
"A400": "#bdbdbd",
"A700": "#616161" // to darkest
}
// So with this color you can do that:
const theme = createTheme({
palette: { primary: grey }
});
Typography
The typography object comes with 13 variants by default:
- h1
- h2
- h3
- h4
- h5
- h6
- subtitle1
- subtitle2
- body1
- body2
- button
- caption
- overline
Each of these variants can be customized individually (Remember that the Material components use these variables, it is recommended that when replacing the fonts you maintain a certain consistency in terms of font sizes)
const theme = createTheme({
typography: {
subtitle1: {
fontSize: 12,
},
body1: {
fontWeight: 500,
},
button: {
fontStyle: 'italic',
},
},
});
If you need to import local sources, remember that the import must be done from the MuiCssBaseline component
import RalewayWoff2 from './fonts/Raleway-Regular.woff2';
const theme = createTheme({
typography: {
// set typography for all variants
fontFamily: 'Raleway, Arial',
},
components: {
MuiCssBaseline: {
styleOverrides: `
@font-face {
font-family: 'Raleway';
font-style: normal;
font-display: swap;
font-weight: 400;
src: local('Raleway'), local('Raleway-Regular'), url(${RalewayWoff2}) format('woff2');
unicodeRange: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF;
}
`,
},
},
});
Shadows
You can also define the shadows that are used throughout the application. You will have to pass the 25 shading options, remember that in position 0 it should go 'none', and as the position grows, the shading should be more intense
const theme = createTheme({
shadows: [
'none',
'0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)',
...
'0px 11px 15px -7px rgba(0,0,0,0.2),0px 24px 38px 3px rgba(0,0,0,0.14),0px 9px 46px 8px rgba(0,0,0,0.12)'
]
});
Transitions
You can customize the durations and easings of the various transitions used across Material UI components
const theme = createTheme({
transitions: {
// Duration of transitions
duration: {
shortest: 150,
shorter: 200,
short: 250,
// most basic recommended timing
standard: 300,
// this is to be used in complex animations
complex: 375,
// recommended when something is entering screen
enteringScreen: 225,
// recommended when something is leaving screen
leavingScreen: 195,
},
// Easing of transitions
easing: {
// This is the most common easing curve.
easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
// Objects enter the screen at full velocity from off-screen and
// slowly decelerate to a resting point.
easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)',
// Objects leave the screen at full velocity. They do not decelerate when off-screen.
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
// The sharp curve is used by objects that may return to the screen at any time.
sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
},
},
});
Crea nuevas transiciones de manera consistence con el metodo transitions.create()
theme.transitions.create(
props: string | string[],
options: Partial<{ duration: string | number; easing: string; delay: string | number }>
)
// Usage:
theme.transitions.create(['background-color', 'transform'], {
duration: theme.transitions.duration.standard,
}
zIndex
Several Material UI components utilize z-index, employing a default z-index scale in Material UI that has been designed to properly layer drawers, modals, snackbars, tooltips, and more. Customization of individual values is discouraged; should you change one, you likely need to change them all. So, proceed with caution.
mobile stepper: 1000fab: 1050speed dial: 1050app bar: 1100drawer: 1200modal: 1300snackbar: 1400tooltip: 1500
const theme = createTheme({
zIndex: {
appBar: 1100,
drawer: 1200,
...
}
});
Theme provider
You need to use the ThemeProvider component in order to inject a theme into your application. (This is optional, Material UI components come with a default theme.)
Example with Create React App:
import { createTheme, ThemeProvider } from '@mui/material/styles';
// theme/index.js
export const theme = createTheme({ ... });
// index.jsx
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import theme from './theme';
...
root.render(
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>,
);
Check the list of examples to see other example projects using Typescript, Next.js, Preact, CDN, and more.
Custom component creation
Material UI provides different tools to create or customize components, among them the styled method that allows us to create new components from HTML elements or Material UI components, also this method receives the Material UI theme as a prop, which will allow us to use all the characteristics and properties that we have configured in our new component.
// The styled method should be imported from @mui/material
import { styled, Button } from '@mui/material';
styled(
element: string | JSX.Element,
options?: (StyledOptions<MuiComponentProps* & MUIStyledCommonProps<...>> & MuiStyledOptions) | (StyledOptions<MUIStyledCommonProps<Theme>> & MuiStyledOptions)
)(
{ ... } | () => ({ ... })
);
// MuiComponentProps* are the props of the MuiComponent, example in Button that will be ButtonProps
Create a component from an HTML element
You can easily create a component with the styled method by passing a string with the HTML element type as the first prop, when invoking the function you can pass an object or a function
// Usage with object
styled('div')({
display: 'flex',
});
// Usage with method that receive the theme as prop
styled('div')(({ theme }) => ({
padding: theme.spacing(1, 2),
backgroundColor: theme.palette.primary.main
boxShadow: theme.shadows[1]
}));
Customize a Material UI component
Sometimes it is necessary to make some small adjustments that we do not need to affect globally, it is very common that a Hero of a site for example uses buttons or special texts that will not be used in the rest of the site, in this case the simplest thing is to overwrite the Material UI component
// Usage with object
styled(Button)({
textTransform: 'capitalize'
});
// Usage with method that receive the theme as prop
styled(Button)(({ theme }) => ({
color: theme.palette.common.black
}));
2.3. Add new properties to your elements
When we create our own new components, we probably also need to add a variant, the styled method allows us to create our own variants to conditionally style our component.
We can easily pass an object as the second property of the styled method with the shouldForwardProp property which will be a method that will allow us to validate if the prop should be passed to the component when it is rendered or should only be used in the styled function
interface MyDivProps { fullWidth: boolean }
styled('div', {
// Check if the prop should be forwarded
shouldForwardProp: prop => prop !== 'fullWidth'
// Then get the prop
})(({ fullWidth }: MyDivProps) => ({
// And use it to customize conditionally
width: fullWidth ? '100%' : 'auto'
}));
You can also check if you should forward multiple properties
shouldForwardProp: prop => (prop !== 'fullWidth') && (prop !== 'size')
An example using the primary or secondary color palette and the theme (always optional if you declare custom props)
import { Theme } from '@mui/material'
interface MyDivProps {
// The keys of the default palettes
color: 'primary' | 'secondary';
// Theme always should be optional, so that when calling our element it does not ask us for all the properties of the theme
theme?: Theme;
}
const MyBox = styled('div', {
shouldForwardProp: prop => prop !== 'color'
})(({ color, theme }: MyDivProps) => ({
color: theme.palette[color].main // it could be theme.palette.primary.main or theme.palette.secondary.main
}));
<MyBox color='primary'> // color: theme.palette.primary.main
The "&" selector
The "&" selector is really useful for styling different states of a component, its children and/or pseudo selectors.
Imagine for a moment the Mui Alert component, this component can change styles according to the properties that we pass to it, for example the severity property can be error , warning, info and success, each state has its own color, we could easily imagine their classnames:
.MuiAlert-root.MuiAlert-error.MuiAlert-warning...
So, the rendered Alert component with the prop severity as error would look like this:
<div class="MuiAlert-root MuiAlert-error">
Ups, this is an error.
</div>
So in our code we could customize the error state of the Material Alert component by wrapping this component in the syled method and concatenating its classes
import { Alert, styled } from '@mui/material'
const StyledAlert = styled(Alert)(({ theme }) => ({
// this is our root (.MuiAlert-root)
fontSize: 12,
// concat the class
// (Note that there is no space between & and .MuiAlert-root)
'&.MuiAlert-error': {
// change the background of alert when it severity is error
backgroundColor: purple[200],
}
}))
<Alert severity="error" />
Now imagine that our Alert component has a child, which is a Material UI Icon, the rendered svg inside the Alert looks like this:
<div class="MuiAlert-root MuiAlert-error">
<svg class="MuiSvg-root">...</svg>
Ups, this is an error.
</div>
We simply have to leave a space between the & and the class name, just like in css:
const StyledAlert = styled(Alert)(({ theme }) => ({
// Note the space between & and .MuiSvg-root
'& .MuiSvg-root': {
// change the background of alert when it severity is error
backgroundColor: purple[200],
}
}))
And we will use the same logic for the pseudo selectors:
const StyledAlert = styled(Alert)(({ theme }) => ({
// You can use many selector at the same time
'&:hover, &:focus, &:active': {
// change the background of alert when it severity is error
backgroundColor: theme.palette.primary.main,
// and also we can change the childs styles when hover on the parent
'& .MuiSvg-root': {
fill: theme.palette.primary.light
}
}
}))
Foundations
The foundations of the theme conceptually refers to each atom that we will use to create our theme, they are the smallest particles and it is very useful to have them unified in a single source of truth. This will allow us to use the same foundations to style different parts of the theme.
Going back to the component example:
theme/
├ index.js
└ components/
├ index.js
├ MuiDialog.component.js
└ MuiAlert.component.js
In this case both components in this example need to use the same background color, so in your foundations folder you can create a colors file and then call it in the different files, this way you will have a single source of truth and you can easily update the background color of both components
theme/
├ index.js
├ components/
│ ├ index.js
│ ├ MuiDialog.component.js
│ └ MuiAlert.component.js
└ foundations/
├ index.js
├ colors.js
└ borders.js
// theme/foundations/colors.js
export const colors = {
red: {
50: '#FFF5F5',
...
500: '#E53E3E',
...
900: '#63171B',
},
}
// theme/foundations/index.js
import colors from "./colors.js"
export const foundations = { colors }
// theme/components/MuiDialog.component.js
import foundations from '../foundations';
...
MuiButton: {
styleOverrides: {
backgroundColor: foundations.color.red[500]
}
}
// theme/components/MuiAlert.component.js
import foundations from '../foundations';
...
MuiAlert: {
styleOverrides: {
backgroundColor: foundations.color.red[500]
}
}
Useful links
- mui.com/material-ui: Complete guide of Material UI
- material.io: Complete guide of Material System Design (used by designers)