[discussion] Standardizing the sx style language across MUI
See original GitHub issueDuplicates
- I have searched the existing issues
Latest version
- 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 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 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.
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?
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).
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.
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:
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,
},
})
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:6 (3 by maintainers)
Top GitHub Comments
Thanks for the reply @mnajdova, I finally have a few moments to sit down with your comments!
I agree for overriding and defining universal styles at the global level makes perfect sense, but one gap in coverage here is that we canāt explicitly turn a style on and off, like you would with a class name. Consider:
I can make a box have this style via a global theme override, either defining some style there (but not with
sx
notation!), or overriding the Mui class name.But what if I wanted to have a generic utility class for use in some Box elements, or maybe a Container element, or even some Typography. I could create custom components for these using
styled()
:But even here, I have to do this for each different component type. Which is fine, but feels like a lot of overhead compared to a single CSS class.
I think there are several ways to work around this, and maybe if nothing else, having an MUI opinion on what the right way of doing it in the docs is worth even more than trying to make a code change to support something new. (Adding
sx
support to global styles and theme objects notwithstanding!). You allude to this inso maybe thatās the best way to do it, and it should just be prevalent in the docs? The only thing I wasnāt super confident on is the duplication of styles. A CSS class can be written once and used everywhere, but this still looks to me like each component where itās used is going to get its own unique class, potentially duplicating a lot of generated code. If Iām wrong, let me know!
There are differences in
styled()
vs.sx
vs.makeStyles
that really do make context switching hard, which you covered in your reference to consolidating these (yay!), but this doesnāt seem like it covers using those intheme
as well, which means intheme
, we still can only use standard CSS syntax? Context switching the different syntaxes is HARD, and I do really hope we can get to a point where we can use the samesx
superset everywhere.The rest of what youāve replied with mostly makes sense, perhaps the only thing that stands out is that the class naming syntax on
styled()
via theslot
andname
props, which just feels a little awkward, but it works, so š¤·āāļø !Is there a plan to un-experimental the
experimental_sx
?