Material-ui: [ListItem] Custom background color always has overridden by MUI style

Created on 22 Nov 2018  路  11Comments  路  Source: mui-org/material-ui


When I'm trying override background color of root node for ListItem( the attribute selected = {true}), MUI is overriding it by default class from theme (theme.palette.action.selected).
I'd like to know is it normal behaviour or is it a bug (I has fixed it in my local repo and can make pull request)?
Thx

  • [v] This is not a v0.x issue.
  • [v] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior 馃

I'd like that background color of ListItem was 'red'.

Current Behavior 馃槸

The background color of ListItem is 'rgba(0, 0, 0, 0.14)'.

Steps to Reproduce 馃暪

import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import { withStyles } from '@material-ui/core/styles'

const styles = theme => ({
  root: {
  },
  selected: {
    backgroundColor: 'red',
  },
})

function Demo(props) {
  const { classes } = props
  return (
    <List component="nav">
      <ListItem selected={true}
        classes={{
          selected: classes.selected
      }}>
        TEST
      </ListItem>
    </List>
  )
}

export default withStyles(styles)(Demo)

Edit p9ll5rw7w7

demo

Context 馃敠

Your Environment 馃寧

| Tech | Version |
|--------------|---------|
| Material-UI | v3.5.1 |
| React | 16.6.3 |
| Browser | Chrome, Safari |
| TypeScript | No |
| etc. | |

List question

Most helpful comment

With v4.0.0-beta.1 you can do:

import React from "react";
import { List, ListItem, styled } from "@material-ui/core";

const StyledListItem = styled(ListItem)({
  backgroundColor: "blue",
  "&.Mui-selected": {
    backgroundColor: "red"
  }
});

function Demo() {
  return (
    <List component="nav">
      <StyledListItem>default</StyledListItem>
      <StyledListItem selected>selected pseudo-class</StyledListItem>
    </List>
  );
}

export default Demo;

https://codesandbox.io/s/xwor26oqo

Capture d鈥檈虂cran 2019-05-03 a虁 18 02 46

cc @iossocket @ShogunRoss @iwknow @chpinan1128 @ndibek @jay-almaraz @HorizonShadow @TidyIQ
I would love to learn more what would be a great solution for you guys.

All 11 comments

@Rombs The correct solution is the following:

import React from "react";
import { List, ListItem, withStyles } from "@material-ui/core";

const StyledListItem = withStyles({
  root: {
    backgroundColor: "blue",
    "&.Mui-selected": {
      backgroundColor: "red"
    }
  },
})(ListItem);

function Demo() {
  return (
    <List component="nav">
      <StyledListItem>default</StyledListItem>
      <StyledListItem selected>selected pseudo-class</StyledListItem>
    </List>
  );
}

export default Demo;

https://codesandbox.io/s/pk5w045pp7?file=/src/demo.js

Capture d鈥檈虂cran 2019-05-03 a虁 18 02 46

You can learn more in https://material-ui.com/customization/components/#pseudo-classes.

I think you can add !important tag to solve this problem.
const styles = theme => ({ root: { }, selected: { backgroundColor: 'red !important', }, })

With v4.0.0-beta.1 you can do:

import React from "react";
import { List, ListItem, styled } from "@material-ui/core";

const StyledListItem = styled(ListItem)({
  backgroundColor: "blue",
  "&.Mui-selected": {
    backgroundColor: "red"
  }
});

function Demo() {
  return (
    <List component="nav">
      <StyledListItem>default</StyledListItem>
      <StyledListItem selected>selected pseudo-class</StyledListItem>
    </List>
  );
}

export default Demo;

https://codesandbox.io/s/xwor26oqo

Capture d鈥檈虂cran 2019-05-03 a虁 18 02 46

cc @iossocket @ShogunRoss @iwknow @chpinan1128 @ndibek @jay-almaraz @HorizonShadow @TidyIQ
I would love to learn more what would be a great solution for you guys.

This would be fine for me if it worked for makeStyles as well.

I'm trying to create a custom OutlineInput component that changes the border color and icon start adornment color to green if a valid input is entered (right now it's simply considered valid if the value is not empty), as well as change the label and notch to accommodate for a start adornment that doesn't result in the input label starting shrunk.

I can get it to work by forcing overrides using !important but I would prefer if I could instead use it how you described above, like so:

// ::::::::::::::::::::::::::::::::::::::::::::::::
// CSS
// ::::::::::::::::::::::::::::::::::::::::::::::::

const useStyles = makeStyles(
  (theme: Theme): Record<string, CSSProperties> => ({
    inputProps: {
      marginLeft: theme.spacing(1)
    },
    inputLabel: {
      marginLeft: theme.spacing(5.5)
    },
    validColor: {
      color: theme.custom.palette.success
    },
    errorColor: {
      color: theme.palette.error.main
    },
    notch: {
      paddingLeft: theme.spacing(6.5)
    },
    notchedValidBorder: {
      "& $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&:hover $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&$focused $notchedOutline": {
        borderColor: theme.custom.palette.success
      }
    }
  })
);

// ::::::::::::::::::::::::::::::::::::::::::::::::
// Layout
// ::::::::::::::::::::::::::::::::::::::::::::::::

interface LayoutProps {
  readonly id: string;
  readonly label: InputLabelProps["children"];
  readonly labelWidth: OutlinedInputProps["labelWidth"];
  readonly labelRef: InputLabelProps["ref"];
  readonly type?: OutlinedInputProps["type"];
  readonly startAdornmentIcon?: ComponentType<SvgIconProps>;
  readonly isEmpty: boolean;
  readonly isInvalid: boolean;
  readonly toggleEmptyState: ToggleEmptyState;
  readonly errorHelperText?: FormHelperTextProps["children"];
}

const Layout: FunctionComponent<LayoutProps> = ({
  id,
  label,
  labelRef,
  labelWidth,
  type,
  startAdornmentIcon,
  isEmpty,
  toggleEmptyState,
  isInvalid,
  errorHelperText
}) => {
  const classes = useStyles();
  const labelClasses: {} | { root: string; focused: string } = isEmpty
    ? {}
    : { root: classes.validColor, focused: classes.validColor };
  return (
    <FormControl
      fullWidth
      margin="dense"
      variant="outlined"
      error={isInvalid && isEmpty}
    >
      <InputLabel
        ref={labelRef}
        className={classes.inputLabel}
        htmlFor={id}
        classes={labelClasses}
        shrink={!isEmpty}
      >
        {label}
      </InputLabel>
      <OutlinedInput
        id={id}
        classes={{
          root: !isEmpty ? classes.notchedValidBorder : undefined,
          notchedOutline: classes.notch
        }}
        labelWidth={labelWidth}
        notched={!isEmpty}
        type={type}
        inputProps={{
          className: classes.inputProps
        }}
        startAdornment={
          startAdornmentIcon ? (
            <IconAdornment
              icon={startAdornmentIcon}
              props={
                isInvalid && isEmpty
                  ? { classes: { root: classes.errorColor } }
                  : !isEmpty
                  ? { classes: { root: classes.validColor } }
                  : undefined
              }
            />
          ) : (
            undefined
          )
        }
        endAdornment={
          type === "password" ? <ShowPasswordAdornment /> : undefined
        }
        onChange={(event): void => toggleEmptyState(event, id)}
      />
      {errorHelperText && (
        <Collapse in={isInvalid && isEmpty}>
          <FormHelperText error>{errorHelperText}</FormHelperText>
        </Collapse>
      )}
    </FormControl>
  );
};

This doesn't work. A few of the class changes are not applied and results in it looking like this: https://i.imgur.com/cPVUNG8.png

And it has the following console error:

Warning: [JSS] Could not find the referenced rule notchedOutline in makeStyles.

However, if I use the following code then it works fine:

// ::::::::::::::::::::::::::::::::::::::::::::::::
// CSS
// ::::::::::::::::::::::::::::::::::::::::::::::::

const useStyles = makeStyles(
  (theme: Theme): Record<string, CSSProperties> => ({
    inputProps: {
      marginLeft: theme.spacing(1)
    },
    inputLabel: {
      marginLeft: theme.spacing(5.5)
    },
    validColor: {
      color: `${theme.custom.palette.success} !important`
    },
    errorColor: {
      color: theme.palette.error.main
    },
    notch: {
      paddingLeft: `${theme.spacing(6.5)}px !important`
    },
    notchedValidBorder: {
      borderColor: `${theme.custom.palette.success} !important`,
      "&:hover": {
        borderColor: `${theme.custom.palette.success} !important`
      },
      "&:focused": {
        borderColor: `${theme.custom.palette.success} !important`
      }
    }
  })
);

// ::::::::::::::::::::::::::::::::::::::::::::::::
// Layout
// ::::::::::::::::::::::::::::::::::::::::::::::::

interface LayoutProps {
  readonly id: string;
  readonly label: InputLabelProps["children"];
  readonly labelWidth: OutlinedInputProps["labelWidth"];
  readonly labelRef: InputLabelProps["ref"];
  readonly type?: OutlinedInputProps["type"];
  readonly startAdornmentIcon?: ComponentType<SvgIconProps>;
  readonly isEmpty: boolean;
  readonly isInvalid: boolean;
  readonly toggleEmptyState: ToggleEmptyState;
  readonly errorHelperText?: FormHelperTextProps["children"];
}

const Layout: FunctionComponent<LayoutProps> = ({
  id,
  label,
  labelRef,
  labelWidth,
  type,
  startAdornmentIcon,
  isEmpty,
  toggleEmptyState,
  isInvalid,
  errorHelperText
}) => {
  const classes = useStyles();
  const labelClasses: {} | { root: string; focused: string } = isEmpty
    ? {}
    : { root: classes.validColor, focused: classes.validColor };
  return (
    <FormControl
      fullWidth
      margin="dense"
      variant="outlined"
      error={isInvalid && isEmpty}
    >
      <InputLabel
        ref={labelRef}
        className={classes.inputLabel}
        htmlFor={id}
        classes={labelClasses}
        shrink={!isEmpty}
      >
        {label}
      </InputLabel>
      <OutlinedInput
        id={id}
        classes={{
          notchedOutline: clsx(
            classes.notch,
            !isEmpty ? classes.notchedValidBorder : undefined
          )
        }}
        labelWidth={labelWidth}
        notched={!isEmpty}
        type={type}
        inputProps={{
          className: classes.inputProps
        }}
        startAdornment={
          startAdornmentIcon ? (
            <IconAdornment
              icon={startAdornmentIcon}
              props={
                isInvalid && isEmpty
                  ? { classes: { root: classes.errorColor } }
                  : !isEmpty
                  ? { classes: { root: classes.validColor } }
                  : undefined
              }
            />
          ) : (
            undefined
          )
        }
        endAdornment={
          type === "password" ? <ShowPasswordAdornment /> : undefined
        }
        onChange={(event): void => toggleEmptyState(event, id)}
      />
      {errorHelperText && (
        <Collapse in={isInvalid && isEmpty}>
          <FormHelperText error>{errorHelperText}</FormHelperText>
        </Collapse>
      )}
    </FormControl>
  );
};

And here's the result: https://i.imgur.com/x4YoIaV.png

@ryancogswell created a sandbox that shows that there is no issue having this work using withStyles here - https://codesandbox.io/s/vx057jo47 - so it appears that there is no solution to overcome this problem for makeStyles like there is for withStyles, apart from using !important to brute force the change.

The sandbox mentioned is from here: https://github.com/mui-org/material-ui/issues/15524#issuecomment-487816785

@TidyIQ Here's a version of that same sandbox using makeStyles that works just fine: https://codesandbox.io/s/qkwln7q6yq

In your example you have:

    notchedValidBorder: {
      "& $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&:hover $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&$focused $notchedOutline": {
        borderColor: theme.custom.palette.success
      }
    }

but you aren't defining $notchedOutline which is what the error is telling you.

You instead need:

    notchedValidBorder: {
      "& $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&:hover $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&$focused $notchedOutline": {
        borderColor: theme.custom.palette.success
      }
    },
   notchedOutline: {} // This line causes $notchedOutline to be defined

then use this something like:

classes={{
          notchedOutline: classes.notchedOutline,
          root: !isEmpty ? classes.notchedValidBorder : undefined
        }}

As I indicated in my comment in #15524, StackOverflow is the more appropriate venue for this type of question.

Thanks for the help, although this seems to be a very confusing way to do it. For example, I changed the CSS to:

    notchedOutline: {
      paddingLeft: theme.spacing(6.5)
    },
    notchedValidBorder: {
      "& $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&:hover $notchedOutline": {
        borderColor: theme.custom.palette.success
      },
      "&$focused $notchedOutline": {
        borderColor: theme.custom.palette.success
      }
    }

And 1) The paddingLeft on the notchedOutline class and color on the validColor class still requires the !important flag to make them work, and 2) I now get an error saying Could not find the referenced rule focused in makeStyles, which means I also need to add an empty CSS class and it to the list of classes under focused: classes.focused.

I'm not sure of a better way to do this but perhaps adding more detail about these instructions in the documentation would be a good idea.

@TidyIQ Here's a version of that same sandbox using makeStyles that works just fine: https://codesandbox.io/s/qkwln7q6yq

@ryancogswell, But if you pass some props to makeStyles, its not working: https://codesandbox.io/s/outlinedinput-emm9t

But if you pass some props to makeStyles, its not working: https://codesandbox.io/s/outlinedinput-emm9t

@ndeviant This seems like a bug in JSS, but you can work around it by making the empty rules (focused and notchedOutline) functions just as the root rule is a function. It doesn't seem to recognize the existence of non-function rules when it is processing a function rule.

The following works:

const useStyles = makeStyles(theme => ({
  root: ({ borderColor }) => ({
    boxShadow: `1px 1px 4px 2px ${borderColor}`,

    "& $notchedOutline": {
      borderColor: borderColor
    },
    "&:hover $notchedOutline": {
      borderColor: borderColor
    },
    "&$focused $notchedOutline": {
      borderColor: borderColor
    }
  }),
  focused: () => ({}),
  notchedOutline: () => ({})
}));

Edit OutlinedInput

I've logged this as an issue: https://github.com/cssinjs/jss/issues/1213

@ryancogswell This is an issue with Material-UI, not JSS, see #15511. I'm not sure that we should
invest resources in JSS, given: #6115.

@oliviertassinari The solution you presented here works, but I think this terrible. Why do you accept a class for selected item if it is not applied? Why you even put that in the documentation of ListItem if it does not work? Using class nested from root is only way to do it, but looks like hack to me.

@tommybernaciak See https://material-ui.com/customization/components/#pseudo-classes for the why.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TimoRuetten picture TimoRuetten  路  3Comments

pola88 picture pola88  路  3Comments

revskill10 picture revskill10  路  3Comments

ghost picture ghost  路  3Comments

rbozan picture rbozan  路  3Comments