mui / mui/material-ui

[discussion] Standardizing the sx style language across MUI

Open
#29,816 6 comments 4 reactions 1 assignee View on GitHub

@mnajdova is already working on this.

Since Nov 21, 2021.

discussion scope: system
Dominant language
JavaScript
Stars
99.1k
Forks
32.5k
Avg merge
2d 17h
Merged PRs (30d)
106

Description

Duplicates
  • 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,
 },
})

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.