Is There a Performance Difference Between the Sx Prop and the Makestyles Function in Material Ui

Is there a performance difference between the sx prop and the makeStyles function in Material UI?

JSS is slightly faster than Emotion for static styles (i.e. styles that aren't dynamic based on props). JSS is much slower than Emotion for dynamic styles -- Emotion has similar performance for both static and dynamic styles.

You can find information about the performance difference for static styles between JSS and Emotion in the following issues:

  • https://github.com/mui-org/material-ui/issues/22342#issuecomment-696553225
  • https://github.com/mui-org/material-ui/pull/22173#issuecomment-673486269

JSS was about 10% faster than Emotion for static styles. For dynamic styles, JSS was 6 times slower than Emotion in one test the Material-UI team performed, and this is why JSS was eliminated from the list of possible styling engines for v5.

The documentation at https://next.material-ui.com/system/basics/#the-sx-prop contains the following performance information:
































Benchmark caseCode snippetTime normalized
a. Render 1,000 primitives<div className="…">100ms
b. Render 1,000 components<Div>120ms
c. Render 1,000 styled components<StyledDiv>160ms
d. Render 1,000 Box<Box sx={…}>370ms

Why is the `sx` prop so much slower?

As I started to dig into this, I realized that I needed to measure the performance of different scenarios in order to have any confidence in my understanding of the performance aspects of the sx prop.

I believe that the performance information in the MUI documentation was gathered using some variation of this repository: https://github.com/mnajdova/react-native-web. The react-native-web repo was used as a starting point because of its "benchmarks" package which contains a useful framework for measuring the performance of different React element rendering/styling approaches.

I created my own version here: https://github.com/ryancogswell/mui-style-benchmarks. You can use this as a starting point to dig into this further. Below are the measurements I made and my conclusions.

My Results for the "Mount deep tree" Benchmark

This test renders 639 elements with approximately 17 CSS properties each except for the cases ("..._minimal", "..._medium")
which reduce the number of CSS properties to show the performance impact.








































































Styling ImplementationTime in msImplementation Desc
inline-styles22.78No styling engine, just use style prop
mui_sx_full36.89MUI Box sx prop with 17 CSS properties
mui_sx_medium24.09MUI Box sx prop with 9 CSS properties
mui_sx_minimal18.15MUI Box sx prop with 4 CSS properties
mui_styled_box22.38MUI styled MUI Box with 17 CSS properties
mui_styled_box_minimal17.90MUI styled MUI Box with 4 CSS properties
tss_react_makestyles17.10makeStyles from tss-react with 17 CSS properties
mui_styled16.93MUI styled div with 17 CSS properties
mui_styled_minimal13.77MUI styled div with 4 CSS properties
emotion_styled16.69Emotion styled div with 17 CSS properties
emotion_styled_minimal12.76Emotion styled div with 4 CSS properties
emotion_css12.58Emotion css div with 17 CSS properties

Using conditional styles in Material-UI with styled vs JSS

Material-UI v5 uses Emotion for the default style engine and consistently uses styled internally in order to make it easier for people who want to use styled-components instead of Emotion to not have to include both in the bundle.

Though the styled API works fine for a lot of use cases, it seems like a clumsy fit for this particular use case. There are two main options that provide a considerably better DX.

One option is to use the new sx prop available on all Material-UI components (and the Box component can be used to wrap non-MUI components to access the sx features). Below is a modification of one of the List demos demonstrating this approach (with the custom ListItemButton simulating the role of your ListItemLink):

import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";

const ListItemButton = ({
selected = false,
...other
}: ListItemButtonProps) => {
const match = selected;
return (
<MuiListItemButton
{...other}
sx={{ color: match ? "primary.main" : undefined }}
/>
);
};
export default function SelectedListItem() {
const [selectedIndex, setSelectedIndex] = React.useState(1);

const handleListItemClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
index: number
) => {
setSelectedIndex(index);
};

return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<List component="nav" aria-label="main mailbox folders">
<ListItemButton
selected={selectedIndex === 0}
onClick={(event) => handleListItemClick(event, 0)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 1}
onClick={(event) => handleListItemClick(event, 1)}
>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</List>
<Divider />
<List component="nav" aria-label="secondary mailbox folder">
<ListItemButton
selected={selectedIndex === 2}
onClick={(event) => handleListItemClick(event, 2)}
>
<ListItemText primary="Trash" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 3}
onClick={(event) => handleListItemClick(event, 3)}
>
<ListItemText primary="Spam" />
</ListItemButton>
</List>
</Box>
);
}

Edit SelectedListItem Material Demo

The only downside of this approach is that it is currently notably slower than using styled, but it is still fast enough to be fine for most use cases.

The other option is to use Emotion directly via its css prop. This allows a similar DX (though not quite as convenient use of the theme), but without any performance penalty.

/** @jsxImportSource @emotion/react */
import * as React from "react";
import Box from "@material-ui/core/Box";
import List from "@material-ui/core/List";
import MuiListItemButton, {
ListItemButtonProps
} from "@material-ui/core/ListItemButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import InboxIcon from "@material-ui/icons/Inbox";
import DraftsIcon from "@material-ui/icons/Drafts";
import { css } from "@emotion/react";
import { useTheme } from "@material-ui/core/styles";

const ListItemButton = ({
selected = false,
...other
}: ListItemButtonProps) => {
const match = selected;
const theme = useTheme();
return (
<MuiListItemButton
{...other}
css={css({ color: match ? theme.palette.primary.main : undefined })}
/>
);
};
export default function SelectedListItem() {
const [selectedIndex, setSelectedIndex] = React.useState(1);

const handleListItemClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
index: number
) => {
setSelectedIndex(index);
};

return (
<Box sx={{ width: "100%", maxWidth: 360, bgcolor: "background.paper" }}>
<List component="nav" aria-label="main mailbox folders">
<ListItemButton
selected={selectedIndex === 0}
onClick={(event) => handleListItemClick(event, 0)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 1}
onClick={(event) => handleListItemClick(event, 1)}
>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItemButton>
</List>
<Divider />
<List component="nav" aria-label="secondary mailbox folder">
<ListItemButton
selected={selectedIndex === 2}
onClick={(event) => handleListItemClick(event, 2)}
>
<ListItemText primary="Trash" />
</ListItemButton>
<ListItemButton
selected={selectedIndex === 3}
onClick={(event) => handleListItemClick(event, 3)}
>
<ListItemText primary="Spam" />
</ListItemButton>
</List>
</Box>
);
}

Edit SelectedListItem Material Demo

In the app I work on (which I haven't yet started to migrate to v5), I expect to use a combination of styled and Emotion's css function/prop. I'm hesitant to use the sx prop heavily until its performance improves a bit (which I think will happen eventually). Even though it performs "fast enough" for many cases, when I have two options with similar DX available and one is twice as fast as the other, I find it difficult to opt for the slower one. The main cases where I would opt for the sx prop are for components where I want to set CSS properties differently for different breakpoints or similar areas where the sx prop provides much nicer DX than other options.

Related answers:

  • Is there a performance difference between the sx prop and the makeStyles function in Material UI?
  • When migrating to Material-UI v5, how to deal with conditional classes?


Related Topics



Leave a reply



Submit