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.

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 ofthead
,td
as a child oftbody
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).

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:
- How to Create a Material-UI Table Sticky Column in Only Three Lines of Code
- The Easiest Way to Implement Material-UI Table Search and Filter
- How to Customize Material-UI Table Scrolling
- How to Customize Material-UI Table Cell Width
- The Ultimate Guide To Material-UI Table Row Height
- The Ultimate Guide to Material-UI Table Pagination (MUI v5)
- Customizing Material-UI Table Background Color, Border and Font Size
- Material-Table for React – I reviewed this third-party library Table built on top of MUI’s Table. It’s awesome!
- The Essential Guide to MUI Table onClick: Row, Cell, & More
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>
</>
);
}