Nowadays, Material UI is one of the most popular UI libraries for React. By installing the library, developers can introduce Material Design System onto their web pages without some tedious effort.
For example, adding a text field and a button is as easy as:
import { Box, Button, TextField } from "@mui/material";
Then using it as:
<Box>
<TextField label="name" variant="standard" />
<Button variant="contained">Submit</Button>
</Box>
Output:
However, despite having excellent documentation, further customizing Material UI components to fit a company's branding is not trivial. Due to this, web developers new to Material UI may find the library overwhelming. This blog post will briefly address some difficulties a developer may face when getting started with the library. While doing this, we will also go over a quick example to customize a Material UI TextField
component.
Note: This post is a quick start guide for developers new to Material UI. It is NOT documentation for Material UI. For the official documentation, please refer to Material UI's official website. In addition, the post is written when Material UI v5 is the latest stable version. Please consider the timeliness of information when reading this blog post.
How to Style?
There are so many ways to style with Material UI. The library gives enough freedom to developers, so you can choose how to style your web page based on your circumstances.
Stying with Styling Libraries
If you are familiar with other styling libraries, such as Styled Components, Tailwind CSS, or Emotion, Material UI has good support for these styling engines. You can start using these libraries by installing them normally.
After installing your preferred styling library, please keep an eye on the <style>
tags under the <head>
of your page. We can utilize the "inspect elements" feature on the browser and take a look:
The style tags in the image above are injected by Material UI. Depending on your styling library, if Material UI injects styles after your chosen styling library, some of your custom styles might be overridden unexpectedly.
This is one of the most common problems that developers may encounter with Material UI. To make it even worse, there is no checker in place to log error messages or warnings. Therefore, it is important to realize the CSS injection order and make corrections before we continue.
Assuming in our index.js
file, we have:
import { App } from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root")
);
To correct the injection order, we can utilize the StyledEngineProvider
component exported by Material UI, as follows:
import { StyledEngineProvider } from "@mui/material";
// ...
root.render(
<StyledEngineProvider injectFirst>
<App />
</StyledEngineProvider>
)
This will fix the injection order, allowing the Material UI styles to appear first in the <head>
section, followed by your custom styles.
Styling with Plain CSS
If you are looking for the traditional approach, you may also style with plain CSS. Just add a class name to your component and style it with a CSS file.
Example.style.css
.button {
background-color: #000;
}
Example.jsx
import "./Example.style.css";
export const Example = () => {
return (
<Box>
<TextField label="name" variant="standard" />
<Button variant="contained" className="button">
Submit
</Button>
</Box>
)
}
Output:
Styling with Material UI Systems
In addition to the two methods above, you can add some quick styles with helpers exported by Material UI. Some people prefer this method more than the previous ones, as it may be the most intuitive for one to work with all Material UI features.
Each component exported by Material UI accepts an sx
prop. You can add custom CSS here, as follows:
<Box>
<TextField label="name" variant="standard" />
<Button variant="contained" sx={{ backgroundColor: "#000" }}>
Submit
</Button>
</Box>
This will produce the same effect as in the plain CSS section.
The sx
prop allows more features, such as accessing the global theme
element that you may configure with ThemeProvider
exported by Material UI. Read more about themes in the Material UI official documentation.
<Box sx={{ margin: 16, padding: 8 }}>
<TextField label="name" variant="standard" />
<Button
variant="contained"
sx={{
backgroundColor: "#000",
marginLeft: (theme) => theme.spacing(2),
}}
>
Submit
</Button>
</Box>
Output:
Note the left margin of the button.
In fact, even the Box
element that we are using up to this point is part of the "Material UI System" components. "Box" is a wrapper component for styling purposes, while one can also use other components such as "Container", "Grid", or "Stack" to produce complex layouts. For example,
<Stack spacing={2}>
<TextField label="name" variant="standard" />
<Button variant="contained">Submit</Button>
</Stack>
Output:
Learn more about Material UI System here: Official MUI Documentation.
Styling with "makeStyles"
If you are a developer migrating from Material UI v4, you may ask, "how about styling with makeStyles
and useStyles
?" While these utilities are deprecated in Material UI v5 and won't work unless you install deprecated packages, we can always make these utility functions ourselves.
const useStyles = () => ({
button: {
backgroundColor: "#000",
},
});
and remember the sx
prop that we introduced above? We can still do classes = useStyles();
, then assign classes.button
to the sx
prop.
Example: Customizing Text Field
Styling work in the real world often needs to match a branding guideline, so it will be more complicated than just using Material UI out of the box. Suppose that we want to have a text field that looks like the following:
Of course, looking at all variants of the Material UI text field, it is quite different:
In general, we follow the following steps when customizing a Material UI component:
- Determine the scope of the customization;
- Determine the component states/classes that will be affected;
- Complete the customization
Determine the scope of the customization
First, we need to understand the scope of our customization. Depending on the answer, one might choose to solve the customization using one of the following methods:
i. Styling from the ground up using the base package. Material UI has a base package, which hosts the "unstyled" MUI components. This is beyond the scope of this blog post;
ii. Expanding a Material UI component to access the lower-level components. For example, the Material UI TextField
component is an abstraction of FormControl
, InputLabel
, FilledInput
, OutlinedInput
, Input
, and FormHelperText
(Source: Material UI Text Field API Docs).
iii. Styling directly on the component using properties and class names accessible.
In our case, we can style directly on the TextField
component.
Determine the component states/classes that will be affected
We will customize the hover state and the focused
state of the text field. Note that Material UI generates a list of global CSS class names. For the focused state, we should customize using the .Mui-focused
class.
Complete the customization
We will base our customization off a Filled Input.
CustomizedTextField.jsx
We start by adding some styles for the text field.
const getRootStyles = (theme) => ({
"& .MuiFilledInput-root": {
border: `1px solid ${theme.palette.grey["A200"]}`,
overflow: "hidden",
borderRadius: theme.spacing(0.5),
transition: theme.transitions.create([
"border-color",
"background-color",
"box-shadow",
]),
"&:hover": {
backgroundColor: "transparent",
},
"&.Mui-focused": {
backgroundColor: "transparent",
borderColor: theme.palette.primary.main,
},
},
});
Note that we are using the theme
object. The theme
object is managed the the Material UI library, and we can get theme
by calling the hook useTheme
. useTheme
is exported by "@mui/material".
const CustomizedTextField = (props) => {
const theme = useTheme();
const root = getRootStyles();
const { sx, InputProps, ...otherProps } = props;
return (
<TextField
variant="filled"
InputProps={{ disableUnderline: true, ...InputProps }}
sx={{ ...root, ...sx }}
{...otherProps}
/>
);
};
and finally
export { CustomizedTextField }
If you wonder where we get class names such as .MuiFilledInput-root
, we can get them by using the official documentation or the "inspect element" function in your browser.
We can now import CustomizedTextField
from CustomizedTextField.jsx
and start using it.
The icon at the end of the text field came from the inputAdornment
prop. One can specify the input adornment when they use the custom component that we just exported. For example,
import { InputAdornment } from "@mui/material"
import { Badge } from "@mui/icons-material";
import { CustomizedTextField } from "./CustomizedTextField"
<CustomizedTextField
label="name"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Badge style={{ color: "#C3C3C3" }} />
</InputAdornment>
),
}}
/>
We're all set! We have now completed the custom text field that we wanted at the beginning of this section.
Final Words
I hope this blog post is helpful for your Material UI journey. Thank you for your time checking out my post!
Top comments (1)
You can also do theme overrides (including default props) at app level, using the components property of the
createTheme()
function. mui.com/material-ui/customization/...