The Ultimate Guide to Styling and Customizing Material-UI Accordion

The Material-UI Accordion (previously known as the expansion panel) is commonly used for controlling when secondary components or information is displayed.

In this example, we will build an Accordion that has components nested in both the summary and details sections and discuss the different components that compose an Accordion (AccordionSummary, AccordionActions, AccordionDetails).

We will also add common stylings to the Accordion (width, height, border, etc) and style the Accordion differently whether it is expanded or contracted.

Accordion with background color, elevation, and a custom icon

Code Sandbox with full React code is in the Resources section.

Material-UI Accordion and Related Components

The Material-UI Accordion is really a wrapper around nested helper components. Not all nested components are required with every accordion. Below is a list of the components and their functions:

  • Accordion – This component wraps the helper components and controls props such as defaultExpanded and a couple of props related to the expansion animation.
  • AccordionSummary – This component is required for expansion functionality on clicks (but not required for rendering). It also contains the expandIcon.
  • AccordionDetails – This component is optional for full functionality. However, it comes with default styling that you most likely want, so I recommend using it.
  • AccordionActions – This is an optional wrapper used to contain action buttons/icons/etc. It comes with a useful spacing prop for default spacing of action components.

Accordion JSX for Our Example

The JSX for this example is quite verbose, partially due to extra divs for aligning the sliders just right. My example is a fork of this example from the docs.

<div className={classes.root}>
  {/*elevation is  not listed in docs but is available*/}
  <Accordion 
    defaultExpanded 
    elevation={3} 
    className={classes.accordion}
  >
    <AccordionSummary
      expandIcon={expanded ? <RemoveIcon /> : <AddIcon />}
      onClick={() => {
        setExpanded(!expanded);
      }}
    >
      <div className={classes.column}>
        <Typography 
          className={classes.heading}
        >
          Settings
        </Typography>
      </div>
      <div className={classes.column}>
        <Typography className={classes.secondaryHeading}>
          Primary Setting
        </Typography>
        <FormControlLabel
          style={{ width: "100%" }}
          aria-label="Acknowledge"
          onClick={(event) => event.stopPropagation()}
          onFocus={(event) => event.stopPropagation()}
          control={
            <Slider 
              value={primaryValue}
              onChange={handlePrimaryChange} 
            />
          }
        />
      </div>
    </AccordionSummary>
    <AccordionDetails className={classes.details}>
      <div className={classes.column} />
      <div className={classes.column}>
        <Typography className={classes.secondaryHeading}>
          Secondary Setting
        </Typography>
        <FormControlLabel
          style={{ width: "100%" }}
          aria-label="Acknowledge"
          onClick={(event) => event.stopPropagation()}
          onFocus={(event) => event.stopPropagation()}
          control={
            <Slider
              value={secondaryValue}
              onChange={handleSecondaryChange}
            />
          }
        />
      </div>
      <div className={clsx(classes.column, classes.helper)}></div>
    </AccordionDetails>
    <AccordionActions>
      <Button size="small">Cancel</Button>
      <Button
        size="small"
        onClick={() => {
          setPrimaryValue(0);
          setSecondaryValue(0);
        }}
      >
        Reset
      </Button>
      <Button size="small">Save</Button>
    </AccordionActions>
  </Accordion>
</div>

Notice that even though elevation isn’t mentioned in the docs, it is an accepted prop on the Accordion. This is because Paper is actually the root element of the Accordion, per the API docs. Adding elevation is a shorthand for adding shadow styling to the Accordion. Shadow can also be accomplished by directly applying box-shadow styling via css.

Notice how I’ve used all the subcomponents discussed above to contain sliders, text, and action buttons. I’ve also used added a state management Boolean (expanded) to track expansion state. Normally this isn’t needed. However, I wanted to swap out the open/close icon depending on state. I also had to disable the default transition applied to the expandIcon, and I’ll discuss this shortly in the styling section below.

Finally, when the Slider components are clicked, we have to stop that click event from propagating and accidentally contracting the Accordion. This is accomplished with the FormControlLabel‘s onClick={(event) => event.stopPropagation()} (the MUI docs did a good job of demonstrating this).

Accordion Styling – Expanded and Closed

Material-UI handily adds a class called Mui-expanded to the Accordion and AccordionSummary components when the Accordion is expanded. This makes styling the different states relatively simple.

Take a look at my JSS code below.

root: {
  width: "100%",
    "& .Mui-expanded": {
      transform: "rotate(0deg)",
      backgroundColor: "rgb(245, 240, 237)"
    }
  },
  accordion: {
    minHeight: 150, //ugly but works
    height: "100%"
  },
  icon: {
    verticalAlign: "bottom",
    height: 20,
    width: 20
  },
  details: {
    alignItems: "center",
    border: "1px solid rgba(0,0,0,0.1)",
    borderRadius: 4
  }

Notice the syntax in the root class. The "& .Mui-expanded" selector will target all children of root class (which is on a div containing the Accordion) that have Mui-expanded class. MUI handles adding the class for us on expansion and removing it on contraction.

Importantly, since I used a custom icon, and a different icon for open/closed states, I needed to remove the default transform and rotate that MUI added on the icon.

Now that we’ve discussed the mechanics of styling in expanded or contracted, let’s look at specific stylings.

  • Accordion width: this is easy to customize. Simply target the wrapping div.
  • Background color: I left background color unchanged when the Accordion is closed. I added background to the wrapping div and this colored the whole component. Alternatively I could have targeted the Accordion component.
  • Height: I don’t recommend changing height. I did in my demo just to show that it is possible. If you want to do it, add a minimum height and then leave the height attribute at 100%. The minimum height will only affect the Accordion when it is closed, as long as the minimum is less than the total natural vertical size when expanded. However, it’s best to simply let the Accordion take its vertical size based on its internal components.

Material-UI Accordion Vs Expansion Panel

You may have used or encountered the Expansion Panel component previously. What’s the difference between the Expansion Panel component and the Accordion? Nothing but the name.

Per Material-UI Docs

⚠️ The ExpansionPanel component was renamed to Accordion to use a more common naming convention.

You should use import { Accordion } from '@material-ui/core' or import Accordion from '@material-ui/core/Accordion'.

Source: MUI ExpansionPanel Docs

Resources

Think you’re a JavaScript expert? Test yourself on these 50 difficult JavaScript questions.

Code Sandbox:

Share this post:

Leave a Comment

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