We’re Too sx-y for Our Code: Why You Should Ditch MUI’s sx Prop in Your React Components for Theming

Anthony Trama
ASHTech
Published in
9 min readAug 24, 2023
Runway model wearing a dress with “SX” lettering spelled out in lights on the ceiling
Photo by Yogendra Singh on Unsplash. SX lighting by Abraham Barrera

I remember the first time I really injured myself. I was probably six years old and my parents were in the backyard, while I was in the garage. I loved playing around the workbench and exploring my dad’s tools. My dad did a great job of keeping the environment safe so I could play with limited supervision. But that afternoon, I reached across the depth of the workbench and found a utility tool that he used when building his remote-controlled airplanes. It was shiny and calling my name: the X-Acto knife! I started digging into his workbench and watched chips of wood come off with a smile on my face.

Then…

Slice!

The edge of the blade cut the pad of my thumb on the way up. I screamed and dropped the blade as I ran to find my parents who were already running into the garage. Fortunately, it didn’t warrant a trip to the hospital and I’m using that thumb to hit the space bar as I type this. But that memory is implanted in my head.

The problem wasn’t the X-Acto knife. It’s a useful tool. Put in the right hands, in the right situation, it works great for its intended use. Used in the wrong manner like, oh, maybe taking chips out of your dad’s workbench, it can be effective and fun, but can also lead to disastrous results. And maybe having your Ninja Turtles taken away for a couple days.

Image of a kid holding a firework with his dad
I wasn’t always a dangerous kid playing with a knife unattended. Sometimes — when my parents used more caution — we were given fireworks instead.

So, What Does This Have to Do With MUI and React?

I’ve written about how we chose React as our framework and how we started migrating our front end to it. One of the tools we decided to use was MUI, which is a React component library built on Material Design (Google’s open-source design system). At ASHTech, we have our own design system, Eve, which is built on top of Material. So, as our engineers started creating components and pages in React, they were matching mockups that were designed to follow Eve, which was (and still is) being developed asynchronously.

Let’s Talk About sx

There are multiple ways to customize or inject styles into an MUI component. This post is only focusing on two: the sx prop and theming. sx is just a prop that allows you to define styles. It looks similar to the style attribute in HTML, but works differently. Those styles can use numbers (e.g., 300), strings with rules similar to CSS (e.g., 1px solid #702F81), or properties of an MUI theme (e.g., secondary.main, which is a color property from the MUI theme). It’s very easy to match a mockup. Just find the rules and add them via sx and voilà!

<Slider
sx={{
width: 300,
border: '1px solid #702F8A',
color: 'secondary.main',
}}
/>

It’s usually the first tool most people reach for when styling an MUI component. It’s like an X-Acto knife: it’s easy to use, simple, versatile, can match any style exactly, and can do more than its intended use.

It can also hurt you.

MUI even states in their documentation that the sx prop is used for a single instance of a component.

The sx prop is the best option for adding style overrides to a single instance of a component in most cases. It can be used with all Material UI components.

The Problem With sx

The problem is that at ASHTech, like many organizations, we want our components to have consistency across many — if not all — instances where they are used. After all, that’s one of the main reasons we’re using a component library in the first place! We have over a dozen development teams that create reusable React components for our websites. The team that is building the <Registration /> component also needs to use the smaller building block components like <TextField />(MUI’s input), <Button />, etc. that the teams building the <ContactUs />, <MyAccount />, <OnlineWorkouts />, components will also use. We want those to look similar.

You cannot have that consistency if you’re relying heavily on the sx prop without manually copy/pasting exactly the same props in every instance…and remembering to make any future updates in all those places. I’ve worked in this industry for a long time and with a lot of smart people and I have yet to see somebody who can do this at scale. Nor should they. Engineers should focus on bigger problems rather than remembering the 50 places to copy/paste props to.

Which reminds me what one of those smart front end engineers on our team said recently when it clicked for him why we were trying so hard to stay away from using sx too often (paraphrasing):

I’ve realized that my job as a front end engineer isn’t to match a mockup exactly. That feels good and is cool to look at, but where I’m most effective is building products for our users that are useful, easy to use, and consistent and spend my time on the bigger issues like building features, improving experience, or performance rather than matching each component to the pixel one at a time.

Bingo! Our users would appreciate a more secure, reliable, faster, and delightful feature over a custom border radius in approximately 100.00% of cases.

sx !== Evil

I want to be clear that I’m not advocating never using sx. For instance, an organization like ours might want the same <Navigation /> component to have some sx props since it is a one-off from other components. Other components might need a width, height, or even margin prop.

X-Acto knife
X-Acto knife

Put the Knife Down and Back Away

So how do we create our own design system and have flexibility within MUI, while keeping components consistent and allowing for future updates to be in sync when we update our styles?

MUI’s createTheme function and ThemeProvider component allow us to do that. MUI’s documentation states that

Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.

Here’s a basic example of how a theme works. You can create a theme with as few as one custom property (see palette.primary.main example below), override as many default MUI theme properties as you want, or even create your own properties.

const theme = createTheme({
palette: {
primary: {
main: purple[500],
},
},
});

<ThemeProvider theme={theme}>
<App />
</ThemeProvider>

Real World Example

Let’s say your mockup has a button with a filled in background and the text “sign up.” If you used MUI’s <Button /> component with contained variant (to make the UI changes easier to see in this example), you would wind up with this:

MUI Button with a blue background and the text “sign up”
MUI’s out-of-the box Button component and theme with the text “Sign Up”

The code to get this would be <Button variant="contained">Sign up</Button>. If you’re not familiar with MUI, a variant is just a prop you can assign to a component that gives it different UI, sort of like a class. The API documentation for each component lists the available variants and which are default. As with all examples going forward, you can see the sandbox below or by clicking the previous link. All the MUI implimentation in my examples are in Demo.js.

However, your design system (or mockups if you don’t officially have a design system yet) calls for a purple background with slighly different padding.

MUI Button with a purple background and the text “sign up”
Example button we’re going to create using MUI theming

You can just pull out the X-Acto knife and carve up a button like this (sandbox):

<Button
variant="contained"
sx={{
padding: ".5rem 1.5rem",
backgroundColor: "#702F8A"
}}
>
Sign up
</Button>

Or you can use a custom theme to change all your buttons. Or at least all buttons of a certain variant (sandbox). We’ll break down the code below.

const theme = createTheme({
palette: {
secondary: {
main: "#702F8A",
light: "#FFEAFF",
dark: "#612A80"
}
},
components: {
MuiButton: {
defaultProps: {
color: "secondary"
},
styleOverrides: {
root: {
padding: ".5rem 1.5rem"
}
}
}
}
});

export default function BasicButtons() {
return (
<ThemeProvider theme={theme}>
<Button
variant="contained"
// sx={
// {
// padding: ".5rem 1.5rem",
// backgroundColor: "#702F8A"
// }
// }
>
Sign up
</Button>
</ThemeProvider>
);
}

Ok, so what’s going on here? First, we’re using createTheme to create a new theme that will actually extend the MUI default, so you don’t have to redeclare all the standard MUI theme properties. In that theme, we’re assigning new colors for secondary.main, secondary.light, and secondary.dark in the palette object. Light and dark have nothing to do with light and dark mode — there is support for that as well — but that’s beyond the scope here. This changes the standard secondary color from a different purple to our brand purple, but you can use any color you want. So any MUI components that already have secondary colors assigned to them by default in MUI will now be purple. We just saved a lot of sx color props!

Next, we’re modifying the components object in the theme. In this example, we’re only modifying the button component. We’re using the key of MuiButton which you can find in any component’s API documentation to add a default prop (defaultProps) of a secondary color to all buttons. This is the eqivalent of using <Button color="secondary"> in every usage. Now you just need to use <Button>, but that can be overwritten in a specific instance by using <Button color="primary"> or another color.

The third change we made was to add custom padding to all buttons via the styleOverrides key. This is like targeting all button elements in a classic CSS stylesheet with a custom padding property like button {padding: ".5rem 1.5rem"}. You can see which sx props are no longer needed with the code that is commented out in the example above.

What About Multiple Variations of a Component

This code works fine for changing all buttons, but what if you only want a specific variant to have styles applied? Let’s suppose your design system wants<Button variant="outlined"> to have a specific background color? In this example, MUI outlined buttons don’t have a background color, but our design system requires them to have a partially transparent purple color. Remember, we only only want this color on outlined buttons, not others (sandbox).

The first set of contained and outlined buttons are with the current theme changes, while the second set shows what our design system calls for. Yes, the design system choice is uglier here, but this is just for demo purposes ;)

Fortunately, MUI gives us access to ownersState, where we can access public and internal props on the component, such as variant! The MUI website has great documentation on this that I won’t try to replicate. For this example, we’re able to target outlined buttons only and change the background color.

import { 
+ alpha,
createTheme,
ThemeProvider
} from "@mui/material/styles";

const theme = createTheme({
//...other theme settings...
components: {
MuiButton: {
//...other button customizations...
styleOverrides: {
root: ({ ownerState }) => ({
padding: ".5rem 1.5rem",
+ ...(ownerState.variant === "outlined" && {
+ backgroundColor: alpha(theme.palette.secondary.main, 0.2)
+ })
})
}
}
}
})

Keep in mind that root targets the root of all buttons. We want consistent padding on all buttons, which is why padding is in root. This solution works great for targeting variants that already exist on the component, but one issue we have yet to find an ideal solution for is adding new variants to components and targeting those with the theme.

It’s possible and MUI outlines how to do this, but there are certain components (e.g., <Alert/>) where adding a new variant loses all of the base colors and styles on the component. In this example, we have to duplicate the default MUI styles which works fine in the first instance, but you have to maintain your styles and sync those with any future MUI updates. We’ve decided it’s a better use of our time to just use the variants MUI gives us in those cases.

How Did We Do This?

In order to do this at scale across a dozen or so teams on a dozen or so websites with different themes that share common components (e.g., our login component, gym search, streaming workouts), it took more than just building and using themes as MUI shows in their docs.

Learning how to use themes was the simple part.

Learning how to toggle themes in the way we wanted to use them was the difficult part.

But for the leadership team, steering over a dozen teams to change the way they write code, while creating designs and building a design system was the really difficult part. In the next part of this series, I’ll explore how we did that.

--

--

Anthony Trama
ASHTech
Editor for

I lead the front end team at ASH. Human and dog dad who eats more burgers than the average person and likes building things out of wood. Living in San Diego.