How to Use the MUI Collapse Component (3 Examples!)

The MUI Collapse component is an invisible wrapper that handles transition animation. It can be added to Tables, Cards, and almost any other MUI component to create an expandable area.

I created three examples with the collapse component: two expand a Table Row and one expands the content area of a Card.

Here’s a screenshot of the Card example. The grey area is the CardContent component that can be hidden:

MUI Collapse Card

The Collapse component is used ‘under the hood’ to control Accordion Expand and Collapse.

Here are the MUI Collapse component API Docs.

MUI Table Expand and Collapse Row on Icon Click

Expanding and collapsing Table rows is a common use case for the Collapse component.

MUI Table Collapse Row
MUI Table Collapse Row

The challenge is with the Collapse component in the Table is that the Collapse wraps it’s child components in several divs. This makes it difficult to get the content of the collapse area the same width as the content of the original Table cells.

MUI Collapse DOM (3 divs deep)
MUI Collapse DOM (3 divs deep)

I tried several methods of integrating the Collapse into the Table. The best method is simply to wrap the Collapse in its own TableRow and TableCell and set a colSpan on the TableCell.

The TableRow and TableCell will be mostly invisible unless their child content is expanded. I only needed to remove the TableCell’s default border.

React is clever enough not to render the invisible TableCell contents when they are not expanded. This significantly reduces the DOM weight.

Collapsed content is not rendered until needed
Collapsed content is not rendered until needed

Here’s a MUI Table tutorial.

import { useState } from "react";
import {
  Box,
  Collapse,
  IconButton,
  Paper,
  SxProps,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
} from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import { faker } from "@faker-js/faker";

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

const addresses: Array<Address> = [];

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

const tableContainerSx: SxProps = {
  width: "max-content",
  marginLeft: "auto",
  marginRight: "auto",
  marginTop: 4,
  borderRadius: 2,
};

export default function ExpandTable() {
  const [open, setOpen] = useState(-1);
  return (
    <TableContainer component={Paper} sx={tableContainerSx}>
      <Table>
        <TableHead>
          <TableRow sx={{ backgroundColor: "rgba(211,211,211,.2)" }}>
            <TableCell>Collapsed?</TableCell>
            <TableCell scope="header">Street Address</TableCell>
            <TableCell>Zip Codes</TableCell>
            <TableCell>City</TableCell>
            <TableCell>State</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {addresses.map((address, index) => (
            <>
              <TableRow key={address.streetAddress}>
                <TableCell>
                  <IconButton
                    aria-label="expand row"
                    size="small"
                    onClick={() => setOpen(open === index ? -1 : index)}
                  >
                    {open === index ? (
                      <KeyboardArrowUpIcon />
                    ) : (
                      <KeyboardArrowDownIcon />
                    )}
                  </IconButton>
                </TableCell>
                <TableCell>{address.streetAddress}</TableCell>
                <TableCell>{address.zipCode}</TableCell>
                <TableCell>{address.city}</TableCell>
                <TableCell>{address.state}</TableCell>
              </TableRow>
              <TableRow>
                <TableCell colSpan={5} sx={{ paddingBottom: 0, paddingTop: 0, border: '0px'}}>
                  <Collapse in={open === index} timeout="auto" unmountOnExit>
                    <Box
                      sx={{
                        width: "100%",
                        backgroundColor: 'rgba(50,50,50,0.4)',
                        minHeight: 36,
                        textAlign: 'center',
                        alignItems: 'center',
                        fontSize: 18
                      }}
                    >
                      It's me! Index: {index}.
                    </Box>
                  </Collapse>
                </TableCell>
              </TableRow>
            </>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

In this example, the state value and the Collapse only allow one row to be expanded at a time. I set open to -1 when all rows should be collapsed.

If I open was an array instead of a single value, it allows multiple rows to be expanded at once.

I also created a DataGrid with expanded rows using the Collapse component.

MUI Table Expand/Collapse Multiple Rows

I can allow multiple rows to be independently expanded and collapsed by maintaining an array instead of a single row value in the open state value.

We can use the same code as above but we’ll make the following adjustments to the useState instantiation and the click handler. Notice the TypeScript type declaration on the state value.

const [open, setOpen] = useState<Number[]>([]);
const handleClick = (clickedIndex: Number) => {
  if(open.includes(clickedIndex)){
    const openCopy = open.filter((element) => {return element !== clickedIndex});
    setOpen(openCopy);
  } else {
    const openCopy = [...open];
    openCopy.push(clickedIndex);
    setOpen(openCopy);
  }
}

The handleClick function now checks the open array for a value.

If the value exists, the user is trying to close a row and the value needs to be removed from the array. If the value is not in the array, the user has clicked a collapsed row and it needs to be expanded by adding the clicked index to the state array.

One other small change is now we conditionally expand or collapse the Collapse component if the open array includes a given index value:

<Collapse in={open.includes(index)} />

I also moved the onClick handler from the IconButton to the TableRow so that a click anywhere on the row expands or collapses the row. I did this just as a UX design decision.

<TableRow key={address.streetAddress} onClick={() => handleClick(index)}>

Here’s a guide to MUI Table row height.

MUI Card Expand and Collapse

In this example I have a MUI Card with a CardHeader action that triggers an expand or collapse of the CardContent on click. Here’s every CardHeader prop explained.

MUI Card with Expand/Collapse in CardHeader

The Collapse need to wrap the CardContent component. However, this makes it difficult to style the CardContent because the Collapse renders as a div and takes up some space.

To fix this, I wrapped the Collapse in a div. It takes up visual space only when the CardContent is expanded. To test why I did this, add background color to the CardContent and then add it to the div that wraps the Collapse component and note the difference.

import { useState } from "react";
import Card from "@mui/material/Card";
import CardHeader from "@mui/material/CardHeader";
import CardContent from "@mui/material/CardContent";
import Container from "@mui/material/Container";
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";

export default function ExpandCard() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Card sx={{ minWidth: 300, border: "1px solid rgba(211,211,211,0.6)" }}>
        <CardHeader
          title="Expand Me!"
          action={
            <IconButton
              onClick={() => setOpen(!open)}
              aria-label="expand"
              size="small"
            >
              {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
            </IconButton>
          }
        >
        </CardHeader>
        <div style={{ backgroundColor: 'rgba(211,211,211,0.4)'}}>
        <Collapse in={open} timeout="auto" unmountOnExit>
          <CardContent>
            <Container sx={{ height: 36, lineHeight: 2 }}>This is the Card Content Area</Container>
          </CardContent>
        </Collapse>
        </div>
      </Card>
    </>
  );
}
Share this post:

Leave a Comment

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