React MUI v5 Table Tutorial: TypeScript, Styling, and Component Cells

The Material-UI Table component is a great data visualization component with incredible customization options. However, sometimes we need a foundational guide for a component. This tutorial explores the subcomponents that compose a table, styling the table the MUI v5 way, TypeScript, and a few Table props.

We will create the table pictured below. The random data comes from faker-js, an easy 3pl for generating data.

MUI Table Tutorial
MUI Table Tutorial

If you want a tutorial for the MUI Data Grid, read this.

Full code for this tutorial is in the Resources section. I have also explored everything possible with the MUI Table (I mean it!) and there are links to eight more articles in the Resources section. YouTube version of this tutorial here.

View a YouTube version of this post or watch below:

MUI Table Subcomponents

A typical table in Material-UI is composed of several subcomponents. These each render as HTML elements familiar to developers who have built tables in pure HTML. Below are the components I used and the elements they render as by default:

  • TableContainer – div
  • Table – table
  • TableHead – thead
  • TableBody – tbody
  • TableRow – tr
  • TableCell – th as a child of thead, td as a child of tbody

The default can be changed for the TableCell, for example it can render as a th instead of a td in the body.

The screenshot below captures the DOM from the TableContainer (the top div) down through some of the TableCells in the TableHeader (the th elements).

MUI Table DOM
MUI Table DOM

Each component type has unique props, but the Table component has the most props and is the core of the Material-UI Table.

I created a MUI Table with Edit and Delete in this tutorial since those features were missing from MUI.

MUI Table Height and Sticky Header

Setting a sticky header on tables with large amounts of data enables the viewer to easily reference column titles while scrolling. There are two primary requirements for setting a sticky header:

  • Set a fixed height for the TableContainer (and make sure inner content has more height than the container)
  • Set stickyHeader={true} on the Table component

The universal requirement in web development for making a scroll bar appear is for outer height or width to be less than inner height or width.

In our tutorial, this is accomplished with a fixed height on the TableContainer. I applied 500px height through the sx prop. The table content will be taller than 500px if more than seven rows of data are generated.

The sticky header (or fixed header) is applied using the stickyHeader prop, but what’s really happening is position: sticky is being applied to each th element in the TableHeader.

MUI Table Links and Buttons

MUI Link and MUI Button be children of a TableCell and rendered in each row. Here’s a side-by-side example of the code:

//Link
<TableCell scope="row">
  <Link
    color="secondary"
    href={`https://www.google.com/search?q=zip+code+${address.zipCode}`}
    target="_blank"
  >
    {address.zipCode}
  </Link>
</TableCell>

//Button
<TableCell scope="row">
  <Button
    sx={{width: 200}}
    variant="outlined"
    color="secondary"
    href={`https://www.google.com/maps/place/${address.state}`}
    target="_blank"
  >
    {address.state}
  </Button>
</TableCell>

For both of these, I took some of the faker-js data and render it as the text of the link or button. I also use string interpolation to include the data in the url for the link and button.

The links and buttons in the table are functional and open a new web page with the faker data. However, the zip codes are truly random and may not be a legitimate value.

When the Button component has an href value, it acts as a link. Both the Button and the Link have variant options, but the Button variants (naturally) look like web buttons while the Link variants reflect different HTML elements (i.e. h1, h2).

The color styling was achieved with the color prop. This prop has quick access to the theme and is using theme.palette.secondary for the color value.

MUI Table Styling

When you wrap an MUI Table with TableContainer, most of your styling will likely be on the container. In my tutorial, I applied sizing and positioning for the table at the TableContainer level.

const tableContainerSx: SxProps = {
  border: "1px solid rgba(128,128,128,0.4)",
  width: "max-content",
  marginLeft: "auto",
  marginRight: "auto",
  marginTop: 4,
  borderRadius: 2,
  maxHeight: 500
};

The next most common components to style are the TableHead and TableBody. These can be used to uniformly style major sections of the table. I applied a background color to every other table row in the body with the following nth-of-type code:

<TableBody
  sx={{
    "& tr:nth-of-type(2n+1)": {
      backgroundColor: "grey.100",
    },
  }}
>

MUI Table with TypeScript

TypeScript does not often directly impact the JSX of a UI. Instead, if impacts helper functions such as event handlers and values such as style consts.

In this Material-UI Table tutorial, the faker-js data and the styling const tableContainerSx both need TypeScript typing.

I created an interface called Address for typing the data. This interface requires all objects pushed into the data array to have the following fields and value types:

interface Address {
  streetAddress: string;
  secondaryAddress: string;
  zipCode: string;
  city: string;
  state: string;
}

The tableContainerSx const needs to be typed with the SxProps type imported from "@mui/material".

const tableContainerSx: SxProps = {...}

SxProps has lots of preconfigured values and value types. Some of these are reflected in the styles I mentioned above, such as width and marginLeft.

Resources

Here are all the ways I’ve customized the Material-UI Table:

Take a look at the Ant Design component library’s table as an alternative to the MUI table.

Full code for this example:

import {
  Button,
  Link,
  Paper,
  Stack,
  SxProps,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
} from "@mui/material";
import { faker } from "@faker-js/faker";

interface Address {
  streetAddress: string;
  secondaryAddress: string;
  zipCode: string;
  city: string;
  state: string;
}

const addresses: Array<Address> = [];

for (let i = 0; i < 20; i++) {
  addresses.push({
    streetAddress: faker.address.streetAddress(),
    secondaryAddress: faker.address.secondaryAddress(),
    zipCode: faker.address.zipCode(),
    city: faker.address.city(),
    state: faker.address.state()
  });
}

const tableContainerSx: SxProps = {
  border: "1px solid rgba(128,128,128,0.4)",
  width: "max-content",
  marginLeft: "auto",
  marginRight: "auto",
  marginTop: 4,
  borderRadius: 2,
  maxHeight: 500
};

export default function TutorialTable() {
  return (
    <>
      <TableContainer
        component={Paper}
        sx={tableContainerSx}
      >
        <Table stickyHeader={true}>
          <TableHead sx={{ "& .MuiTableCell-stickyHeader": {backgroundColor: "primary.main"} }}>
            <TableRow>
              <TableCell scope="header">Street Address</TableCell>
              <TableCell scope="header">Zip Code</TableCell>
              <TableCell scope="header">City</TableCell>
              <TableCell scope="header">State</TableCell>
            </TableRow>
          </TableHead>
          <TableBody
            sx={{
              "& tr:nth-of-type(2n+1)": {
                backgroundColor: "grey.100",
              },
            }}
          >
            {addresses.map((address) => (
              <TableRow key={address.streetAddress}>
                <TableCell scope="row">
                  <Stack direction="column">
                    <div>{address.streetAddress}</div>
                    <div>{address.secondaryAddress}</div>
                  </Stack>
                </TableCell>
                <TableCell scope="row">
                  <Link
                    color="secondary"
                    href={`https://www.google.com/search?q=zip+code+${address.zipCode}`}
                    target="_blank"
                  >
                    {address.zipCode}
                  </Link>
                </TableCell>
                <TableCell scope="row">{address.city}</TableCell>
                <TableCell scope="row">
                  <Button
                    sx={{width: 200}}
                    variant="outlined"
                    color="secondary"
                    href={`https://www.google.com/maps/place/${address.state}`}
                    target="_blank"
                  >
                    {address.state}
                  </Button>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
    </>
  );
}

Table Docs

Table API Docs

Share this post:

Leave a Comment

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