The Essential Material-UI Tutorial

Material-UI is the most dynamic and thorough React component library available today.  If you are developing serious React applications, you should consider using MUI.  This tutorial will show you how to build an app with the following MUI features:

  • Form Input
  • Table-Based Output
  • MUI Styling
    • Theming
    • Styled Components/API
    • SX prop-based styling
  • AppBar/Drawer Architecture
  • MUI Integration with React-Router
  • Stack Component Layout

Here is a gif showing the UI we will build:

MUI Tutorial

First I will give an overview of the Material-UI documentation, run through an obligatory intro for installing MUI, and give a brief tutorial on the MUI styling system.

After this we will create the working UI described above. The form will populate the Table with records through a simple “store” (a shared array).

As I reference styling systems and components in this tutorial, I will link to comprehensive articles that discuss each topic further. A Code Sandbox with full React code is in the Resources section.

A Review of the Material-UI Docs

The MUI docs are very thorough for basic examples, but sometimes more complex situations in MUI are difficult to resolve even with the docs.

The docs follow this pattern for each component:

  • A handful of examples
  • Links to live demos for each example
  • A separate “API” page that describes each prop on the component, as well as what classes are available for styling the component

Examples that I had difficulty resolving even with the docs include adding a border to the MUI TextField on hover and styling the MUI Autocomplete’s Popper.

By my count, there are 50+ primary components in MUI, each with documentation. Additionally, there are dozens of supporting components (i.e. TableContainer, TableRow, TableColumn for Table) that have their own docs.

There are also extensive docs supporting the styling system, theming, other utilities, and experimental components.

Check out the MUI component docs here.

Installing Material-UI (MUI V5)

To start, I simply built a new React app using create-react-app.

Then, I add these MUI-specific dependencies for MUI v5:

"@mui/material": "^5.4.0",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0"
  • @mui/material – this is the primary dependency for MUI components
  • @emotion/react and @emotion/styled – MUI styling uses emotion by default.

If you are still using Material-UI V4, the main dependency is "@material-ui/core": "^4.11.4". You do not need emotion for V4.

In each file you will see the import statement for the required components.

MUI Styling System: The sx Prop and the Styled API

Before using Material-UI it is important to have an understanding of the two main styling APIs: the sx prop and the styled API.

The sx prop behaves similarly to the makeStyles hook of MUI v4. It also is similar to inline styling. Take a look at the below example:

<Button sx={{bgColor: 'primary.main', width: 300}}>Click Me!</Button>

This will apply a background color and width. The sx prop has access to the ‘MUI system’, which has shorthand for accessing theme values (palette, zIndex, shadow, and more).

Read a detailed guide to MUI’s sx prop here.

The styled API is an integration with the styled-components library. It can do all the same things the sx prop can do. Generally components created using the styled API are meant to be exported and reused (their styling is more abstracted), while components utilizing the sx prop are one-off styling situations.

A tiny example of creating a styled component with a custom backgroundColor is below:

const StyledPaper = styled(Paper, {
  name: "StyledPaper"
})({
  backgroundColor: "#6B8068"
});

The styled API has many options and customizations available.

Read a detailed guide to MUI’s styled API here.

MUI App Architecture – AppBar, Drawer, and Routing

Here’s a simple design pattern that this Material-UI tutorial will follow:

  • AppBar at the top
  • Drawer with navigation Links on the left side
  • A main element filling the rest of the space. This element contains the view or component that has been routed to.

This is a useful design for many MUI apps, both large and small. I like to call it the ‘Scaffolding’ of the app, and that’s also what I have named the component that contains this code.

The Scaffolding is available below, and I will explain the details of the implementation in this section. The Form and Table code will be discussed in separate sections.

import Drawer from "@mui/material/Drawer";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import AppBar from "@mui/material/AppBar";
import List from "@mui/material/List";
import ListItem from "@mui/material/List";
import ListItemText from "@mui/material/ListItemText";
import Input from "./Input";
import Output from "./Output";
import Avatar from '@mui/material/Avatar';
import {
    Routes,
    Route,
    Link,
    BrowserRouter,
} from "react-router-dom";

const drawerWidth = 100;


const Scaffolding = (props) => {
    return (
        <BrowserRouter>
            <div>
                <AppBar 
		    sx={{ 
		        zIndex: (theme) => theme.zIndex.drawer + 1 }}
		>
		    <Toolbar>
		        <Typography variant="h4" sx={{ flexGrow: 1 }}>MUI Tutorial</Typography>
		        <Avatar sx={{ bgcolor: 'magenta' }}>JM</Avatar>
		    </Toolbar>
		</AppBar>
                <Drawer
                    variant="persistent"
                    open={true}
                    ModalProps={{
                        keepMounted: true,
                        hideBackdrop: true
                    }}
                    PaperProps={{ 
		        elevation: 12, 
			sx: { bgcolor: 'primary.light', width: drawerWidth, textAlign: 'center', borderRight: 0 } }}
                >
                    <Toolbar />
                    <nav >
                        <List>
                            <ListItem button>
                                <Link to="/input">
                                    <ListItemText primary="Input" />
                                </Link>
                            </ListItem>
                            <ListItem button>
                                <Link
                                    to={{
                                        pathname: "/output",
                                    }}
                                >
                                    <ListItemText primary="Output" />
                                </Link>
                            </ListItem>
                        </List>
                    </nav>
                </Drawer>
                <main style={{ marginLeft: drawerWidth + 12, marginTop: 12 }}>
                    <Toolbar />
                    <Routes>
                        <Route path="/" element={<Input />} />
                        <Route path="input" element={<Input />} />
                        <Route path="output" element={<Output />} />
                    </Routes>
                </main>
            </div>
        </BrowserRouter>
    );
}

export default Scaffolding;

I’ll discuss the react-router code last, since it is outside the scope of Material-UI (but it has some useful integrations.

MUI AppBar

The AppBar provides a container and default styling for our top utility section. The MUI Toolbar goes hand-in-hand with the AppBar and provides vertical height and styling for the section.

Notice on the AppBar that I set the z-index ‘1’ higher than the Drawer component z-index. This ensures that the AppBar sits nicely ‘on top’ of the Drawer.

Here’s a guide to the AppBar component.

Also important to notice is the flex grow value on the Typography component in the Toolbar. The effect of this flex grow is that the Avatar is pushed all the way to the right.

Typography is a useful styling component that comes with lots of stylings available in the default theme. These can be accessed with the variant prop, i.e. variant="h4".

MUI Drawer

Take a look at the use of the sx prop on the Drawer’s PaperProps. PaperProps allows props to be passed into the Paper component (which is rendered immediately inside the Drawer’s top level div). In this example with sx, I am using a shorthand for backgroundColor, for accessing the theme, and for setting the borderRight value.

The Drawer by default has a Modal. I have disabled that using ModalProps.hideBackdrop.

The Drawer also uses a Toolbar. This is important simply for pushing the Drawer items ‘lower’ than the AppBar on the y axis, otherwise they’d be hidden.

Here’s a guide to the Drawer component.

MUI List and ListItem

The List component is perfect for a list of navigable links. I did not dive deep into custom styling the List or ListItems, but you can read how to style and use ListItem here.

React-Router Integration with Material-UI

I am using version 6 of react-router in this MUI tutorial.

React-Router has a handy integration component called the Link. Notice that I have wrapped Links inside ListItems, and have them wrapping ListItemText. These are both common MUI components.

The other two points of integration are:

  • Wrapping the app in BrowserRouter
  • Creating the <Routes><Route/></Routes> structure inside the main element.

Make sure to get the path/element syntax correct in the Route components, that gave me a bit of difficulty.

Notice how the first Route has path=”/”. This is the default when no URL route has been specified by the user.

Material-UI Form Page

MUI doesn’t have it’s own form component. Instead, it uses the React <form/> element and supplies a variety of form subcomponents.

We will examine them below, but first let’s discuss the architecture of our MUI Form example:

  • There are three form subcomponents
  • Each one populates it’s respective state value via an onChange handler
  • The ‘Save’ button click pushes this data to a ‘Store’ array that the Table consumes from
  • Most of the styling is provided by a custom theme, which is made available by a ThemeProvider. This will be discussed in a separate section
import React, { useCallback, useState } from "react";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Autocomplete from "@mui/material/Autocomplete";
import RadioGroup from "@mui/material/RadioGroup";
import Radio from "@mui/material/Radio";
import FormControlLabel from "@mui/material/FormControlLabel"
import FormControl from "@mui/material/FormControl"
import FormLabel from "@mui/material/FormLabel"
import { ThemeProvider } from "@mui/material/styles";
import { FormTheme } from "../Theme/FormTheme";
import { developers } from "../Store/Store";

const formStyle = {
  border: `2px solid ${FormTheme.palatte.primary.main}`, 
  borderRadius: '4px',
  padding: 12, 
  maxWidth: 400
}

const Input = (props) => {

  const [devName, setDevName] = useState('');
  const [devTech, setDevTech] = useState('');
  const [devWork, setDevWork] = useState('owner');

  const handleTextFieldChange = (event) => {
    setDevName(event.target.value);
  }

  const handleAutoCompleteChange = (event, value) => {
    setDevTech(value);
  }

  const handleRadioChange = (event) => {
    setDevWork(event.target.value);
  }

  const handleButtonClick = useCallback(() => {
    developers.push({name: devName, tech: devTech, work: devWork});
  }, [devName, devTech, devWork]);

  return (
    <ThemeProvider theme={FormTheme}>
    <form style={formStyle}>
      <Typography variant="h6">Developer Info:</Typography>
      <TextField id="name" label="Name" variant="outlined" onChange={handleTextFieldChange} />
      <Autocomplete
        options={["React", "Angular", "Vue", "jQuery", "Something Else"]}
        getOptionLabel={(option) => option}
        renderInput={(params) => {
          return (
            <TextField
              {...params}
              variant="outlined"
              label="JS Tech"
            />
          );
        }}
        onChange={handleAutoCompleteChange}
      />
      <Stack direction='row' sx={{justifyContent: 'space-between', alignItems: 'flex-end'}}>
      <FormControl component="fieldset" variant="filled">
          <FormLabel component="legend" htmlFor="work-type-radio">
            Work Situation
          </FormLabel>
          <RadioGroup
            aria-label="work"
            id="work-type-radio"
            defaultValue="Self-Employed"
            name="radio-buttons-group"
            onChange={handleRadioChange}
          >
            <FormControlLabel
              value="Self-Employed"
              control={<Radio />}
              label="Self-Employed"
            />
            <FormControlLabel
              value="Employee"
              control={<Radio />}
              label="Employee"
            />
            <FormControlLabel value="Freelancer" control={<Radio />} label="Freelancer" />
          </RadioGroup>
        </FormControl>
        <Button variant='contained' onClick={handleButtonClick}>Save</Button>
        </Stack>
    </form>
    </ThemeProvider>
  );
};

export default Input;

MUI TextField

The TextField is composed of several other MUI components, such as the Label and Input components. It has several variants and lots of default styling.

All of the DOM elements in the dev tools window are part of the highlighted TextField.

MUI TextField DOM
MUI TextField DOM

The TextField is the go-to component for free-form text input. It can be challenging to style because of it’s complexity, but here’s an introduction to default styling and here’s how to style the border on hover.

MUI Autocomplete

The Autocomplete is composed of a TextField and a dropdown (which uses a Popper component). Because of this composition, if you can figure out how to style the TextField you should be able to style part of the Autocomplete.

The Popper is much more challenging to style. Also, the props for the Autocomplete are somewhat complex. In my example, I used as few as possible:

  • options
  • getOptionLabel
  • renderInput

My example was also a list of strings. If you need to populate the dropdown with a list of objects, you need a few more props. This tutorial for the Autocomplete component should get you the information you need.

MUI Stack

The MUI Stack component is used for controlling simple, one-dimensional layouts. In v4, you might have used the Box component for one-dimensional layouts.

The most important prop for the Stack component is the direction prop. It controls whether the layout is vertical or horizontal. In my tutorial code, I set direction="row" for a horizontal layout. This set the button to the right of the Radio FormControl.

Stack has a display value of ‘flex’ by default. I added the following sx flex values:

  • justifyContent: 'space-between' – pushed the button to the ‘end’
  • alignItems: 'flex-end' – pushed the button to the ‘bottom’

Here’s a guide to the Stack component.

MUI FormControl and RadioGroup

The radio buttons in my form are composed of five different subcomponents. I’ll discuss the primary two components here. Read this post which explains every MUI form component.

The FormControl manages the state of the encapsulated components. For example, if the FormControl is disabled, all the children will be disabled. The FormControl could even be considered a replacement for the form element for basic forms.

The RadioGroup manages the onChange handlers for all the radio children, and it controls their layout. If I had preferred a horizontal layout, I would have set row={true}. Below is the result:

MUI RadioGroup
RadioGroup row={true}

Here’s a guide to managing your form’s layout.

Form Theme

The form Input component in this demo uses sx heavily. However, I also had some universal styling for the form components that I didn’t want to repeat. In that situation, use a theme.

I created a custom theme called FormTheme and exported it for use in the ThemeProvider that wraps the Input’s subcomponents. This makes the style overrides, palette, shadows, and all other customizations available to all child components wrapped by the provider.

import { createTheme } from "@mui/material/styles";

const FormTheme = createTheme({
  palatte: {
    primary: {
      main: '#FDE992'
    }
  },
  components: {
    MuiTypography: {
      styleOverrides: {
        root: {
          margin: 4
        }
      }
    },
    MuiTextField: {
      styleOverrides: {
        root: {
          width: 300,
          margin: 4,
          "& .MuiOutlinedInput-notchedOutline": {
              borderColor: 'green'
          },
          "& .MuiInputBase-root.Mui-focused .MuiOutlinedInput-notchedOutline": {
              borderColor: 'yellow'
          }
        },
      }
    },
    MuiFormControl: {
      styleOverrides: {
        root: {
          margin: 4
        }
      }
    }
  }
});

export { FormTheme };

MUI Theme Palette

MUI theme palette
Custom Material-UI Theme Palette Primary Main Color

The first configuration was setting the theme.palette.primary.main color. Notice the effect this had on the button and radio selector. These components automatically use the primary.main color, so no changes were required for them to pull the new value.

I also set the form border to use theme.palette.primary.main. This border is on the React form element, which does not have access to the theme using shorthand. However, since I imported the FormTheme I was able to access it directly: border: `2px solid ${FormTheme.palette.primary.main}`.

Here’s a guide to the theme palette.

MUI Component Style Overrides

Component default styling can be overridden in a theme using the following syntax:

components.Mui[componentName].styleOverrides.class

For example, I overrode the TextField border color. This applied to both the TextField and the Autocomplete component (since it is composed of a TextField).

Overriding in the theme abstracts away some of the styling code and also keeps the implementation file cleaner.

Material-UI Table Page

The Table is an excellent data layout component for midsize datasets (for large datasets, check out the DataGrid).

Below is the code for the table view in our tutorial:

import React from "react";
import TableContainer from "@mui/material/TableContainer";
import Table from "@mui/material/Table";
import TableHead from "@mui/material/TableHead";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import Paper from "@mui/material/Paper";
import { styled } from "@mui/system";
import { developers } from "../Store/Store";

const StyledTableHeaderCell = styled(TableCell, {
  name: "StyledTableHeaderCell"
})((props) => ({
  color: '#00008b',
  padding: 4,
  fontSize: 16
}));

const StyledTableBodyCell = styled(TableCell, {
  name: "StyledTableBodyCell"
})((props) => ({
  color: '#567d46',
  padding: 2,
  fontSize: 12
}));

const Output = (props) => {

  return (
    <TableContainer 
	    component={Paper} 
	    sx={{
		  width: 'max-content', 
		  minWidth: 300, 
		  "&.MuiPaper-root": { backgroundColor: 'rgba(236, 236, 236, 0.5)'}}}
    >
        <Table aria-label="dev table">
          <TableHead>
            <TableRow sx={{height: 35}}>
              <StyledTableHeaderCell width={200}>Name</StyledTableHeaderCell>
              <StyledTableHeaderCell width={150} align="left">
                Tech
              </StyledTableHeaderCell>
              <StyledTableHeaderCell width={150} align="left">
                Work Situation
              </StyledTableHeaderCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {developers.map((row) => (
              <TableRow sx={{height: 30}} key={row.name}>
                <StyledTableBodyCell component="th" scope="row">
                  {row.name}
                </StyledTableBodyCell>
                <StyledTableBodyCell align="left">{row.tech}</StyledTableBodyCell>
                <StyledTableBodyCell align="left">{row.work}</StyledTableBodyCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
  );
};

export default Output;

Tables are composed of several subcomponents. Usually we find TableContainer, Table, TableHead, TableBody, TableRow, and TableCell used in a table UI.

The Table in MUI is row-focused. As you can see above, we code one row and then loop through the data repeating rendering of rows using that code.

In this case, I used the styled API to create new TableCells: cells for the header and cells for the body. I abstracted the styling away from the JSX and had a cleaner return statement.

There is a lot to learn about Tables and a lot of customization possible. If you want to learn more, take a look at the following articles:

Resources

I published this MUI tutorial as a NPM package if you would like to run it inside an app.

GitHub repository with full React code.

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.