Material-ui: Can't override IconButton disabled color

Created on 3 Dec 2018  路  12Comments  路  Source: mui-org/material-ui

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

Expected Behavior 馃

I should be able to override disabled color.

Current Behavior 馃槸

It is always rgba(0, 0, 0, 0.26)

screenshot 2018-12-03 at 17 43 13

Steps to Reproduce 馃暪

const styles = theme => ({
  button: {
    width: 8 * theme.spacing.unit,
    height: 8 * theme.spacing.unit,
    color: theme.palette.primary.main
  },
  buttonDisabled: {
    color: theme.palette.grey[900]
  },
  icon: {
    fontSize: theme.spacing.unit * 5
  }
});

<IconButton
  disabled={true}
  className={classes.button}
  classes={{ disabled: classes.buttonDisabled }}>
  <PauseIcon className={classes.icon} />
</IconButton>

Your Environment 馃寧

| Tech | Version |
|--------------|---------|
| Material-UI | v3.6.1 |
| React | 16.5.2 |
| Browser | Chrome Version 70.0.3538.110 (Official Build) (64-bit) |

docs important

Most helpful comment

@Gentlee Thank you for raising this concern. So, why do people have to do:
A.

.icon-button-root.icon-button-disabled {
  color: 'grey',
}

/* or even better */

.icon-button-root:disabled {
  color: 'grey',
}

```jsx
disabled
classes={{
root: 'icon-button-root',
disabled: 'icon-button-disabled',
}}
/>

when they want to override the icon button disabled style and not just:
B.
```css
.icon-button-disabled {
  color: 'grey',
}

```jsx
disabled
classes={{
disabled: 'icon-button-disabled',
}}
/>

*If you are using raw CSS and you don't want to repeat the classes, you can use `dangerouslyUseGlobalCSS`.*

This is an explicit design decision, not a bug. It's a very important one. In order to implement B, we need to keep the CSS specificity of the disabled variant at the same level as the default style. Now, let's say you want to change the non-disabled background color. You would inject some CSS after the Material-UI CSS. And just like that, you have overridden the disabled style at the same time. So if you want the disabled background color style back? You need to reapply it 馃槺. To sum-up:
1. We want to avoid this type of unpredictable side effects.
2. It's not consistent with pseudo-classes that increase CSS specificity, e.g. `:disabled`.

I encourage [the following solution](https://codesandbox.io/s/j3nw83628y):
```jsx
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";

const styles = theme => ({
  button: {
    "&:disabled": {
      backgroundColor: "grey"
    }
  }
});

function IconButtons(props) {
  const { classes } = props;
  return (
    <IconButton disabled className={classes.button} aria-label="Delete">
      <DeleteIcon />
    </IconButton>
  );
}

IconButtons.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles)(IconButtons);

The topic is partially covered in https://material-ui.com/guides/api/#css-classes and https://material-ui.com/customization/overrides/#overriding-with-classes.
I'm sorry for not making the documentation clear enough. I can understand your confusion. I think that we should update the documentation to better explain it.

All 12 comments

@gentlee Try this:

const styles = theme => ({
  button: {
    width: 8 * theme.spacing.unit,
    height: 8 * theme.spacing.unit,
    color: theme.palette.primary.main,
    "&$buttonDisabled": {
        color: theme.palette.grey[900]
    }
  },
  buttonDisabled: {},
  icon: {
    fontSize: theme.spacing.unit * 5
  }
});

<IconButton
  disabled
  classes={{ root: classes.button, disabled: classes.buttonDisabled }}>
  <PauseIcon className={classes.icon} />
</IconButton>

@joshwooding this hack works. But in real project i would better not use disabled prop at all.

@Gentlee I believe this is intentional look at overriding internal states: https://material-ui.com/customization/overrides/ and the answer to #13672

@joshwooding custom style should not be overridden by default value. It is a bug. Definitely.

@Gentlee Thank you for raising this concern. So, why do people have to do:
A.

.icon-button-root.icon-button-disabled {
  color: 'grey',
}

/* or even better */

.icon-button-root:disabled {
  color: 'grey',
}

```jsx
disabled
classes={{
root: 'icon-button-root',
disabled: 'icon-button-disabled',
}}
/>

when they want to override the icon button disabled style and not just:
B.
```css
.icon-button-disabled {
  color: 'grey',
}

```jsx
disabled
classes={{
disabled: 'icon-button-disabled',
}}
/>

*If you are using raw CSS and you don't want to repeat the classes, you can use `dangerouslyUseGlobalCSS`.*

This is an explicit design decision, not a bug. It's a very important one. In order to implement B, we need to keep the CSS specificity of the disabled variant at the same level as the default style. Now, let's say you want to change the non-disabled background color. You would inject some CSS after the Material-UI CSS. And just like that, you have overridden the disabled style at the same time. So if you want the disabled background color style back? You need to reapply it 馃槺. To sum-up:
1. We want to avoid this type of unpredictable side effects.
2. It's not consistent with pseudo-classes that increase CSS specificity, e.g. `:disabled`.

I encourage [the following solution](https://codesandbox.io/s/j3nw83628y):
```jsx
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";

const styles = theme => ({
  button: {
    "&:disabled": {
      backgroundColor: "grey"
    }
  }
});

function IconButtons(props) {
  const { classes } = props;
  return (
    <IconButton disabled className={classes.button} aria-label="Delete">
      <DeleteIcon />
    </IconButton>
  );
}

IconButtons.propTypes = {
  classes: PropTypes.object.isRequired
};

export default withStyles(styles)(IconButtons);

The topic is partially covered in https://material-ui.com/guides/api/#css-classes and https://material-ui.com/customization/overrides/#overriding-with-classes.
I'm sorry for not making the documentation clear enough. I can understand your confusion. I think that we should update the documentation to better explain it.

@oliviertassinari ok i see that it is a design decision. Seems your decision forces me to know about implementation (CSS), that means it is a bad decision.

I thought it should be like: root default -> custom default [-> root disabled -> custom disabled]. And no side effects like this.

Anyways, i see that you are not going to change implementation, so thanks for the explanation.

@Gentlee Yes, you have to know some about the underlying DOM and CSS structure.

I have a similar problem and I've tried to follow along here, but I still can't get the disabled or primary colors to change to my preferences. I'm using an Icon in my IconButton, and it doesn't seem to behave the same way. I have:

const colorDisabled = '#F6F6F6';
const colorPrimary = '#656668';

export const AudioIconStyles = createStyles({
    button: {
       fontSize: 15,
        color: colorPrimary,
        '&$buttonDisabled': {
            color: colorDisabled
        }
    },
    buttonDisabled: {},
    icon: {
        fontSize: 15,
    }
});
export interface AudioProps extends WithStyles<typeof AudioIconStyles> {
...
}
export const Audio = withStyles(AudioIconStyles)(
    class extends React.PureComponent<AudioProps, AudioState> {
        render() {
            return (
               <Tooltip title={stoppedTooltipText} placement='bottom-start'>
                   <IconButton
                       className={this.props.classes.button}
                       onClick={this.onStop}
                       id={this.props.id + '-stop'}
                       disabled={this.state.stopped}
                  >
                      <Icon className={classNames(this.props.classes.icon, 'fa fa-stop')}
                          classes={{colorPrimary, colorDisabled}}
                          color={this.state.stopped ? 'disabled' : 'primary'}
                       />
                  </IconButton>
              </Tooltip>
            );
        }
    }
);

But the primary color and disabled color are always the the ones which come from material-ui:
image

I have also tried straight inline CSS, so, like this:

<Icon className='fa fa-stop'
    style={{fontSize: 15, color: colorPrimary}}
/>

But that doesn't work at all. I don't get the font size nor the color. Could you tell me what I'm doing wrong, please?

@kimwykoff try

button: {
       fontSize: 15,
        color: colorPrimary,
        '&:disabled': {
            color: colorDisabled
        }
    },

@joshwooding, I actually did, but it didn't help. The button stuff seemed ok, although it doesn't seem to do much. The real probably is in the Icon component. I couldn't get the color to set to 'disabled'. It just doesn't seem like Icon reacts to any of the styling props you give it.

I finally just removed all the css injection and used simple class names that I defined in my .scss file. I still wasn't getting the disabled color, but I put a '!important' next to my color and it finally works.

The injection stuff is just too time-consuming to figure out. The doc needs a more complete example with explanations.

@kimwykoff Thank you for the feedback. I'm going to update the documentation to better explain the situation.

@kimwykoff I had a look at your example, it can't work. The buttonDisabled rule you are using is creating a corresponding class name, but you are not applying it to the DOM. https://codesandbox.io/s/ov1lv8qkkz

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sys13 picture sys13  路  3Comments

ghost picture ghost  路  3Comments

FranBran picture FranBran  路  3Comments

ghost picture ghost  路  3Comments

pola88 picture pola88  路  3Comments