Repository: material-ui
Issue: [discussion] Standardizing the sx style language across MUI
Labels: discussion,package: system

### Duplicates

- [X] I have searched the existing issues

### Latest version

- [X] I have tested the latest version

### Summary 💡

With the deprecation of `makeStyles()`, there are a few things we lose as far as reusability and fidelity:

- How do we create reusable components with portable styles and props? We used to do this with makeStyles() but now we can’t/shouldn’t?
- Where do we define other non-theme global styles? Maybe some utility classes like text or flexbox alignment, or reusable styles you want to be present throughout your app?
- How do we create reusable styles within a component? sx is cool, but no one wants to copy and paste a bunch of inline styles all over the place, right?
- How do we add classNames? (I realize that there's a debate whether named styles are meaningful, and perhaps they're not).

This post explores some of that rationale, the challenges/pain, and some possible solutions.

### Examples 🌈

_No response_

### Motivation 🔦

_(This is a tactical abstract of a [story](https://brandonscript.design/the-mysterious-case-of-the-disappearance-of-muis-makestyles-9515a58eb9f8) I wrote about this suggestion)._

## The pain

With the deprecation of `makeStyles()`, there are a few things we lose as far as reusability and fidelity:

- How do we create reusable components with portable styles and props? We used to do this with makeStyles() but now we can’t/shouldn’t?
- Where do we define other non-theme global styles? Maybe some utility classes like text or flexbox alignment, or reusable styles you want to be present throughout your app?
- How do we create reusable styles within a component? sx is cool, but no one wants to copy and paste a bunch of inline styles all over the place, right?
- How do we add classNames? (I realize that there's a debate whether named styles are meaningful, and perhaps they're not).

Now, we can of course use `styled()` to wrap components in a style, but even then, that's not a set of reusable styles; they're individually defined on each element, and you'd have to define them again for every component you make.

## Some ways I've considered working around this:

### Workaround 1

Add a custom style property to a theme object and using the spread operator (`...`), mix it into the rest of your style. I don’t mind this, but it doesn’t quite feel the same as adding a class to your component, because we never use the `className` prop. It also doesn’t know anything about the class name you’ve given it, so the generated HTML on the page never includes it in the class tag. 

You also have to be careful with the spread operator and deep copies. Depending on what you’re doing, you might be better off using `deepmerge` here instead.

### Workaround 2

Create a higher scoped variable and reference it from the sx property . The upside to this is it’s fast and we can use the power of sx again (🥳), but the downsides are that we can’t do it globally—it only works in your local component file (unless we made a separate module… maybe?), and the generated code will make a copy of the style for each of the sx references.

But also, while spread/deepmerge are fine, they're not very idiomatic, and there's a fair amount of room for error.

I also have found some [REALLY weird things](https://twitter.com/brandonscript/status/1461777757311049730?s=21) when using pre-defined sx props, and I don't know if this is bug-worthy or not, I can't repro it very well. In this case, I was mixing in a color prop `{ color: 'primary.main' }` with a const `{ flexDirection: 'column' }`. But I digress.

### Workaround 3 

Use an external CSS file. I’m not sure I like this, though:
- Adding an external stylesheet means you (probably) end up with a separate build step to manage your plain CSS. You might lose TypeScript validation checking fidelity here (e.g., a typo in your .css file might appear to be valid in your React component’s `className` prop), and more moving parts means more things that could go wrong.
- You break the opinionated model of React components and the MUI system, which could lead to a bunch of weird problems that are difficult to troubleshoot. External styles load in a different order, so you’ll spend your time wondering why styles aren’t applying, or why others are clobbering things you didn’t intend them to. There’s also the namespace conflict and collision risk.
- Styling the component HTML for MUI components is tough. They often contain multiple nested elements, with MUI-specific names like `.MuiSlider-thumb`, and you’ll have to figure out what they all are. Difficult to maintain, difficult to write.

## Ask

_(Disclaimer: because `sx` is wrapping Emotion, there is maybe another better way of doing this I don't know about. If so, please let me know!)_

I wish there were a way to fix this pain using `sx` everywhere, with true support for reusable styles. Why?

- `sx` is awesome, and I love it.
- **I don’t want to context switch**. I want to know that whatever styles I’m writing are all using the same superset of CSS so that there’s no chance of getting confused. This is arguably the most important of all my pain points.
- I want linting to work everywhere. If I write an `sx` style block in a theme declaration, the system should be able to tell me if I made a mistake. (Maybe less relevant for .js, but definitely for .ts).
- Naming things doesn’t always matter, but it’s often helpful for debugging styles, and coming from a plain HTML/CSS background, not using identifiable class names feels like a loss? Not a deal breaker though.
- MUI is opinionated about a lot of things, but how to declare global styles and utility classes doesn’t seem to be one of them. We should fix that.
- I don’t understand the performance implications of the myriad ways of putting styles into an app. Are some better than others? Probably—and I’d rather the system just told me what to do, rather than me having to think about it.

So how could we make this better? These might be throwaway ideas, but at least could spark some ideas.

### Idea 1

A new hook like `makeStyles()` that uses the sx superset. 

```tsx
import * as React from 'react'
import { styled } from '@mui/system'

const useStyles = styled.sx({
  flexCenter: {
    display: 'flex',
    alignItems: 'center', 
    justifyContent: 'center',
    flexBasis: 'fit-content',
    flexDirection: {
      xs: 'column',
      sm: 'row',
    }
  }
})

export default function MyComponent(props) {
  const classes = useStyles(props)
  return <div className={classes.flexCenter} />
}
```

Or perhaps there's a way we can create variables with all our reusable styles?

```tsx
import * as React from 'react'
import { styled } from '@mui/system'

const flexCenter = styled.sx({
  display: 'flex',
  alignItems: 'center', 
  justifyContent: 'center',
  flexBasis: 'fit-content',
  flexDirection: {
    xs: 'column',
    sm: 'row',
  }
})

const flexCenterDiv = flexCenter(MyComponent)( {
  return <div>My content</div>
})
```

### Idea 2

Support all of the `sx` superset in `theme` objects. (Note that this doesn't solve for reusability).

```tsx
import * as React from 'react'
import { styled, createTheme, ThemeProvider } from '@mui/system'

const MyThemeComponent = styled('div')(({ theme }) => ({
  color: 'primary.contrastText',
  backgroundColor: 'primary.main',
  paddingX: { 
    xs: 1,
    sm: 2,
    md: 3,
  },
  borderRadius: theme.shape.borderRadius,
}))

export default function ThemeUsage() {
  return (
    <ThemeProvider theme={customTheme}>
      <MyThemeComponent>Styled div with theme</MyThemeComponent>
    </ThemeProvider>
  )
}
```

### Idea 3

Create a wrapper or dedicated section in a theme to support utility or global classes.

```tsx
import { createTheme, styled } from '@mui/material/styles'

let theme = createTheme({
  shape: {
    borderRadius: 4,
  },
})

theme = createTheme(theme, {
  classes: styled.sx({
    flexCenter: {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      flexBasis: 'fit-content',
      flexDirection: {
        xs: 'column',
        sm: 'row',
      }
    },
    makeItRound: {
      borderRadius: theme.shape.borderRadius,
    })
  },
})
```

If we didn't want to worry about naming things, this idea works just as well without classNames, if we wanted to define `sx` props at the root:

```tsx
import { createTheme, styled } from '@mui/material/styles'

let theme = createTheme({
  sx: {
    borderRadius: 4,
  },
})

theme = createTheme(theme, {
 sx: {
   display: 'flex',
   alignItems: 'center',
   justifyContent: 'center',
   flexBasis: 'fit-content',
   flexDirection: {
     xs: 'column',
     sm: 'row',
   }
   borderRadius: theme.sx.borderRadius,
 },
})
```