Skip to main content

Introduction

Tables display information in a way that’s easy to scan, so that users can look for patterns and insights. They can be embedded in primary content, such as cards. They can include functionality such as sorting, searching, pagination, and filtering.

Basic Usage

import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

function Demo() {
  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Calories</TableCell>
            <TableCell>Fat (g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          <TableRow>
            <TableCell>Frozen yogurt</TableCell>
            <TableCell>159</TableCell>
            <TableCell>6.0</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>Ice cream sandwich</TableCell>
            <TableCell>237</TableCell>
            <TableCell>9.0</TableCell>
          </TableRow>
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Dense Table

A simple example of a dense table with no frills.
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

function DenseTable() {
  const rows = [
    { name: 'Frozen yogurt', calories: 159, fat: 6.0, carbs: 24, protein: 4.0 },
    { name: 'Ice cream sandwich', calories: 237, fat: 9.0, carbs: 37, protein: 4.3 },
    { name: 'Eclair', calories: 262, fat: 16.0, carbs: 24, protein: 6.0 },
  ];

  return (
    <TableContainer component={Paper}>
      <Table size="small">
        <TableHead>
          <TableRow>
            <TableCell>Dessert (100g serving)</TableCell>
            <TableCell align="right">Calories</TableCell>
            <TableCell align="right">Fat (g)</TableCell>
            <TableCell align="right">Carbs (g)</TableCell>
            <TableCell align="right">Protein (g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row) => (
            <TableRow key={row.name}>
              <TableCell component="th" scope="row">
                {row.name}
              </TableCell>
              <TableCell align="right">{row.calories}</TableCell>
              <TableCell align="right">{row.fat}</TableCell>
              <TableCell align="right">{row.carbs}</TableCell>
              <TableCell align="right">{row.protein}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Sorting & Selecting

This example demonstrates the use of Checkbox and clickable rows for selection, with a custom Toolbar.
import { useState } from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TableSortLabel from '@mui/material/TableSortLabel';
import Checkbox from '@mui/material/Checkbox';
import Paper from '@mui/material/Paper';

interface Data {
  id: number;
  name: string;
  calories: number;
  fat: number;
}

function SortableTable() {
  const [selected, setSelected] = useState<number[]>([]);
  const [orderBy, setOrderBy] = useState<keyof Data>('calories');
  const [order, setOrder] = useState<'asc' | 'desc'>('asc');

  const rows: Data[] = [
    { id: 1, name: 'Cupcake', calories: 305, fat: 3.7 },
    { id: 2, name: 'Donut', calories: 452, fat: 25.0 },
    { id: 3, name: 'Eclair', calories: 262, fat: 16.0 },
  ];

  const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.checked) {
      setSelected(rows.map((n) => n.id));
      return;
    }
    setSelected([]);
  };

  const handleClick = (id: number) => {
    const selectedIndex = selected.indexOf(id);
    let newSelected: number[] = [];

    if (selectedIndex === -1) {
      newSelected = newSelected.concat(selected, id);
    } else if (selectedIndex === 0) {
      newSelected = newSelected.concat(selected.slice(1));
    } else if (selectedIndex === selected.length - 1) {
      newSelected = newSelected.concat(selected.slice(0, -1));
    } else if (selectedIndex > 0) {
      newSelected = newSelected.concat(
        selected.slice(0, selectedIndex),
        selected.slice(selectedIndex + 1),
      );
    }

    setSelected(newSelected);
  };

  const isSelected = (id: number) => selected.indexOf(id) !== -1;

  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell padding="checkbox">
              <Checkbox
                indeterminate={selected.length > 0 && selected.length < rows.length}
                checked={rows.length > 0 && selected.length === rows.length}
                onChange={handleSelectAllClick}
              />
            </TableCell>
            <TableCell>Dessert</TableCell>
            <TableCell align="right">
              <TableSortLabel
                active={orderBy === 'calories'}
                direction={orderBy === 'calories' ? order : 'asc'}
              >
                Calories
              </TableSortLabel>
            </TableCell>
            <TableCell align="right">Fat (g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row) => {
            const isItemSelected = isSelected(row.id);
            return (
              <TableRow
                hover
                onClick={() => handleClick(row.id)}
                role="checkbox"
                aria-checked={isItemSelected}
                tabIndex={-1}
                key={row.id}
                selected={isItemSelected}
              >
                <TableCell padding="checkbox">
                  <Checkbox checked={isItemSelected} />
                </TableCell>
                <TableCell component="th" scope="row">
                  {row.name}
                </TableCell>
                <TableCell align="right">{row.calories}</TableCell>
                <TableCell align="right">{row.fat}</TableCell>
              </TableRow>
            );
          })}
        </TableBody>
      </Table>
    </TableContainer>
  );
}
Here is an example of a table with scrollable rows and fixed column headers.
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

function StickyHeadTable() {
  const rows = Array.from({ length: 20 }, (_, i) => ({
    name: `Item ${i + 1}`,
    calories: 100 + i * 10,
    fat: 5 + i * 0.5,
  }));

  return (
    <TableContainer component={Paper} sx={{ maxHeight: 440 }}>
      <Table stickyHeader>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell align="right">Calories</TableCell>
            <TableCell align="right">Fat (g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row, index) => (
            <TableRow key={index}>
              <TableCell>{row.name}</TableCell>
              <TableCell align="right">{row.calories}</TableCell>
              <TableCell align="right">{row.fat}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Column Grouping

You can group table headers by rendering multiple table rows inside a table head.
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

function GroupedTable() {
  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell rowSpan={2}>Dessert</TableCell>
            <TableCell align="center" colSpan={3}>
              Nutritional Info
            </TableCell>
          </TableRow>
          <TableRow>
            <TableCell align="right">Calories</TableCell>
            <TableCell align="right">Fat (g)</TableCell>
            <TableCell align="right">Carbs (g)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          <TableRow>
            <TableCell>Frozen yogurt</TableCell>
            <TableCell align="right">159</TableCell>
            <TableCell align="right">6.0</TableCell>
            <TableCell align="right">24</TableCell>
          </TableRow>
          <TableRow>
            <TableCell>Ice cream sandwich</TableCell>
            <TableCell align="right">237</TableCell>
            <TableCell align="right">9.0</TableCell>
            <TableCell align="right">37</TableCell>
          </TableRow>
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Collapsible Table

An example of a table with expandable rows.
import { useState } from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import IconButton from '@mui/material/IconButton';
import Collapse from '@mui/material/Collapse';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';

function Row({ row }: { row: { name: string; calories: number; history: any[] } }) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <TableRow>
        <TableCell>
          <IconButton size="small" onClick={() => setOpen(!open)}>
            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
          </IconButton>
        </TableCell>
        <TableCell>{row.name}</TableCell>
        <TableCell align="right">{row.calories}</TableCell>
      </TableRow>
      <TableRow>
        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
          <Collapse in={open} timeout="auto" unmountOnExit>
            <Table size="small">
              <TableHead>
                <TableRow>
                  <TableCell>Date</TableCell>
                  <TableCell>Customer</TableCell>
                </TableRow>
              </TableHead>
              <TableBody>
                {row.history.map((historyRow) => (
                  <TableRow key={historyRow.date}>
                    <TableCell>{historyRow.date}</TableCell>
                    <TableCell>{historyRow.customer}</TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </Collapse>
        </TableCell>
      </TableRow>
    </>
  );
}

function CollapsibleTable() {
  const rows = [
    {
      name: 'Frozen yogurt',
      calories: 159,
      history: [
        { date: '2020-01-05', customer: 'John Doe' },
        { date: '2020-01-02', customer: 'Jane Smith' },
      ],
    },
  ];

  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell />
            <TableCell>Dessert</TableCell>
            <TableCell align="right">Calories</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row) => (
            <Row key={row.name} row={row} />
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

Props

Table Props

PropTypeDefaultDescription
childrennode-The content of the table, normally TableHead and TableBody.
classesobject-Override or extend the styles applied to the component.
padding'normal' | 'checkbox' | 'none''normal'Allows TableCells to inherit padding of the Table.
size'small' | 'medium''medium'Allows TableCells to inherit size of the Table.
stickyHeaderbooleanfalseSet the header sticky.
sxSxProps<Theme>-The system prop for defining CSS overrides and additional styles.

TableCell Props

PropTypeDefaultDescription
align'inherit' | 'left' | 'center' | 'right' | 'justify''inherit'Set the text-align on the table cell content.
childrennode-The content of the component.
classesobject-Override or extend the styles applied to the component.
padding'normal' | 'checkbox' | 'none'-Sets the padding applied to the cell. Inherits from parent Table.
scopestring-Set scope attribute.
size'small' | 'medium'-Specify the size of the cell. Inherits from parent Table.
sortDirection'asc' | 'desc' | false-Set aria-sort direction.
sxSxProps<Theme>-The system prop for defining CSS overrides and additional styles.
variant'head' | 'body' | 'footer'-Specify the cell type. Inherits from parent context.

TableRow Props

PropTypeDefaultDescription
childrennode-Should be valid tr children such as TableCell.
classesobject-Override or extend the styles applied to the component.
hoverbooleanfalseIf true, the table row will shade on hover.
selectedbooleanfalseIf true, the table row will have the selected shading.
sxSxProps<Theme>-The system prop for defining CSS overrides and additional styles.

CSS Classes

Table Classes

  • .MuiTable-root - Styles applied to the root element.
  • .MuiTable-stickyHeader - Styles applied to the root element if stickyHeader={true}.

TableCell Classes

  • .MuiTableCell-root - Styles applied to the root element.
  • .MuiTableCell-head - Styles applied to the root element if variant="head".
  • .MuiTableCell-body - Styles applied to the root element if variant="body".
  • .MuiTableCell-footer - Styles applied to the root element if variant="footer".
  • .MuiTableCell-alignLeft - Styles applied to the root element if align="left".
  • .MuiTableCell-alignCenter - Styles applied to the root element if align="center".
  • .MuiTableCell-alignRight - Styles applied to the root element if align="right".
  • .MuiTableCell-alignJustify - Styles applied to the root element if align="justify".
  • .MuiTableCell-sizeSmall - Styles applied to the root element if size="small".
  • .MuiTableCell-paddingCheckbox - Styles applied to the root element if padding="checkbox".
  • .MuiTableCell-paddingNone - Styles applied to the root element if padding="none".
  • .MuiTableCell-stickyHeader - Styles applied to the root element if table has stickyHeader={true}.

TableRow Classes

  • .MuiTableRow-root - Styles applied to the root element.
  • .MuiTableRow-selected - Styles applied to the root element if selected={true}.
  • .MuiTableRow-hover - Styles applied to the root element if hover={true}.
  • .MuiTableRow-head - Styles applied to the root element if table variant is head.
  • .MuiTableRow-footer - Styles applied to the root element if table variant is footer.

Accessibility

Caption

A caption functions like a heading for a table. Use TableContainer with appropriate labeling:
<TableContainer component={Paper} aria-label="simple table">
  {/* table content */}
</TableContainer>

Sortable Tables

When implementing sortable columns, use TableSortLabel and provide screen reader announcements:
<TableSortLabel
  active={orderBy === 'calories'}
  direction={orderBy === 'calories' ? order : 'asc'}
  onClick={createSortHandler('calories')}
>
  Calories
  {orderBy === 'calories' && (
    <Box component="span" sx={visuallyHidden}>
      {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
    </Box>
  )}
</TableSortLabel>

Selectable Rows

For selectable rows, include appropriate ARIA attributes:
<TableRow
  hover
  role="checkbox"
  aria-checked={isSelected}
  tabIndex={-1}
  selected={isSelected}
>
  <TableCell padding="checkbox">
    <Checkbox checked={isSelected} inputProps={{ 'aria-labelledby': labelId }} />
  </TableCell>
  {/* other cells */}
</TableRow>

Build docs developers (and LLMs) love