'How best to create custom styled components in MUI

seemingly simple problem:

  1. Example Background: we have many lists of items and render them in the UI with <List>.
  2. Example Problem: we usually (but not always) want to remove the extra padding that is injected on <List> (ex: <UnpaddedList>).

As another example, perhaps we want to have most lists be collapsible by default with a <List onClick={...}> trigger to become <CollapsableList>. For most examples below, the unpadded is sufficient to illustrate.

On first guess, I'd expect something like CustomList extends List (but see Facebook inheritance advice). There are many ways to do it, but what are the advantages/disadvantages...they seem to conflict:

createStyled()

This is intended to entirely replace the theme... in times intended to break brand standards. Although we could go back and merge the existing theme, this seems against the purpose of this tool, and deepmerge perhaps could slow performance? this would work well if it could inherit the current theme context. perhaps lots of extra component names is bad practice? ex: List, UnpaddedList UnpaddedLargeList, UnpaddedLargeGreenList, etc.

see https://mui.com/system/styled/#create-custom-styled-utility

createTheme()

this allows total override of styles and props for each component. The only issue is that it may not be clear why a component is styled a certain way and keeps its name. (ex: <List> isn't renamed to <UnpaddedList>)This could be confusing if you have a component that is inheriting a theme from a parent component somewhere up in the tree and appears with an unexpected style. the developer would have to trace each parent component to find where the theme was injected with <ThemeProvider>. But perhaps such injecting sub-themes in unexpected ways would be its own anti-pattern.

Also, without clear docs/typescript, it took a lot of reading to determine how best to pass and modify the current theme:

function extendThemeWithGreen(theme: Theme) {
  let themeOptions: ThemeOptions = { palette: { primary: { main: "green" } } };
  return createTheme(theme, themeOptions);
}

function Example() {
  return <ThemeProvider theme={extendThemeWithGreen}> example</ThemeProvider>;
}

another issue is that some components have arbitrary white-space injected into them. this is not documented and has no type-hinting. the only way to discover those is to hunt down the source code. from there you would likely have to create another set of styles that override the native styles and introduce bloat into the app.

see https://mui.com/system/styled/#custom-components see https://mui.com/customization/how-to-customize/#2-reusable-style-overrides

return <List {...props} />

Wrapping components would be nice. (ex: const UnpaddedList = (props) => <List disablePadding {...props} />;) so far this has been a lot of trouble. for example, should the props be ListProps or OverridableComponent<ListTypeMap<{}, "ul">>? Or suppose both the end component and wrapped component have sx... do I have to set a deepMerge to deal with duplicate props. Maybe I just need to spend more time to get it going?

see https://mui.com/guides/composition/#wrapping-components

sx={...} or style={...} or class=...

these props generally allow customization, however they are effectively one-off. ex: you have to <List style={{padding:0}} /> also, this loses type safety (expect things to break in the future) class can be separate, but still hard to retain type safety. Legacy makeStyles( is similar

see https://mui.com/system/the-sx-prop/ see https://mui.com/styles/api/#createstyles-styles-styles

<UnstyledList component={...}

This is largely an upcoming feature and not fully implemented yet. Although this could avoid future css conflicts, it still doesn't facilitate props override (or if it does... it isn't clear from the docs)

see https://mui.com/customization/unstyled-components/#main-content

<Box component={List} ... />

Use the Box wrapper to create a custom component. The docs aren't entirely clear on this. It may be something like:

function UnpaddedList(props: ListProps){
  return <Box component={List} disablePadding {...props} />
}

Many of the component props have generic modifiers... however it isn't clear how this new component would be written to match (or extend) the original component props

see https://mui.com/system/box/#overriding-mui-components

variant="unpadded"

this is close, however it appears to create a typing issue, or special treatment to import. i could see this causing confusion down the road, but at least linting would throw a warning if not implemented properly. it doesn't have a good way to do composition (ex: variant="unpadded&collapsible"). it also isn't clear how this is done for components that do not have variants built in.

see https://mui.com/customization/theme-components/#adding-new-component-variants

props

of course some, but not all, can be done through props keep type safety. (ex: <List disablePadding={true}>) however this isn't DRY. it is overall worse to manage (no brand standard). it also makes the code much more difficult read and excessively verbose

other methods?

perhaps I missed another way. I also can't find any graceful solution to the hardcoded whitespace in MUI components



Solution 1:[1]

If you have a component that is used in multiple places and only one of them needs to have some special styles or you need to tweak or fix an edge case in a specific layout, use sx prop. It's suitable for one-off styles like this:

<List sx={{ p: 0 }}>

If you need to apply some styles occasionally in a component, use styled. For example if a component is used in 100 places but only in 20 places it needs to have the padding disabled then add a padding prop to enable the styles in the rarer case:

const options = {
  shouldForwardProp: (prop) => prop !== 'disablePadding',
};
const List = styled(
  MuiList,
  options,
)(({ theme, disablePadding = false }) => ({
  ...(disablePadding && {
    padding: 0,
  }),
}));
// I dont always use disablePadding but sometimes I need it
<List disablePadding>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1