1

To customize components using material-ui, you can do this:

const styles = {
  root: {
    '&$disabled': {
      color: 'white',
    },
  },
  disabled: {}, // <-- why important?
};

That compiles to:

.root-x.disable-x {
  color: white;
}

But then the documentation is saying:

⚠️ You need to apply the two generated class names (root & disabled) to the DOM to make it work.

I have found out the hard way that that is true. For example in the snippet below, just remove the disabled class (or remove the disabled key from styles above), and you'll find that your disabled button has no white text anymore.

<Button
  disabled
  classes={{
    root: classes.root, // class name, e.g. `root-x`
    disabled: classes.disabled, // <-- why important?
  }}
>

You can try this out in this sandbox: https://codesandbox.io/s/material-demo-hzqwm

So why is it that you need to apply the two generated class names (root & disabled) to the DOM to make it work?

It only seems natural to define the disabled style like so, but that is not causing a disabled button to have white text.

const styles = {
  root: {},
  disabled: {
      color: 'white',
  },
};

Is there a scenario where you would use this way of setting the disabled style?

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
Christiaan Westerbeek
  • 10,619
  • 13
  • 64
  • 89

1 Answers1

2

You have a few different questions here.

Question 1: Why is disabled: {} necessary in the styles below?

const styles = {
  root: {
    '&$disabled': {
      color: 'white',
    },
  },
  disabled: {}, // <-- why important?
};

The $disabled syntax is the mechanism for referring to another rule in the same style sheet. In the terminology used within JSS, the above block creates a style sheet with two rules: root and disabled. A CSS class name will get generated for each of these rules, and $disabled will resolve to that generated class name. If you leave out disabled: {}, then that rule will not exist in the style sheet (so JSS will not generate a corresponding class name) and JSS won't know what $disabled should resolve to and will tell you this in the console:

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


Question 2: Why is disabled: classes.disabled necessary in the classes prop below?

<Button
  disabled
  classes={{
    root: classes.root, // class name, e.g. `root-x`
    disabled: classes.disabled, // <-- why important?
  }}
>

As you indicated in your question, the styles will compile to something like:

.root-x.disable-x {
  color: white;
}

This will match an element that has both the root-x class and the disable-x class in its class name. If you don't pass disabled: classes.disabled to the Button, then it won't know about the disable-x class name and it won't be applied to the Button; therefore it won't match the CSS rule since the Button will only have root-x in its class name.

The styles will also be applied successfully if you do the following:

<Button disabled className={`${classes.root} ${classes.disabled}`}>

The main benefit of using classes is that then you can always pass the same classes in even if the button isn't currently disabled (e.g. if disabled is in state that can be toggled by some action) and the disabled class will only be applied when appropriate, but in the case where disabled===true, className={`${classes.root} ${classes.disabled}`} has the same effect as classes={{ root: classes.root, disabled: classes.disabled }}.


Question 3: Why can't I define the disabled style like below?

const styles = {
  root: {},
  disabled: {
      color: 'white',
  },
};

The issue here is CSS specificity.

Below is a portion of the default styles for Button:

export const styles = (theme) => ({
  root: {
    color: theme.palette.text.primary,
    '&$disabled': {
      color: theme.palette.action.disabled,
    },
  },

With regard to specificity, this uses a single class selector to define the default color. It then uses the combination of two class selectors to define the disabled color -- this gives the disabled styles greater specificity to ensure that the disabled styles win when the button is in a disabled state.

If the default styles were defined as follows:

export const styles = (theme) => ({
  root: {
    color: theme.palette.text.primary,
  },
  disabled: {
    color: theme.palette.action.disabled,
  },

these two rules would have equal specificity. In that case, whichever one occurs last (with regard to the order of the styles declarations in the <head> element) will win. This would work for the default styles, but if you then decided to override the default color (but not the disabled color) with something like:

const StyledButton = withStyles({
  root: {
    color: 'blue'
  }
})(Button);

These styles would come after the default styles in the head (more details about why explained here) which means after the default disabled styles as well. Then if the disabled and root styles had the same specificity, the color "blue" would be used even when the button was disabled (instead of using theme.palette.action.disabled). Material-UI consistently uses increased specificity for style rules for states other than the default (e.g. disabled, focused, error) to ensure that these styles win over customizations of the default styles, so you need to use the same degree of specificity in your overrides of those other states.


Implied Question 4: Is there an easier way to define the disabled styles?

Yes, you can find in the documentation here the global class names for the "pseudo-classes". For instance the class name used for the disabled state is always Mui-disabled. This means that you can create a Button with a custom disabled look using the following:

const CustomButton = withStyles({
  root: {
    '&.Mui-disabled': {
      color: 'white',
    },
  }
})(Button);

Edit Custom disabled Button

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198