question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Customizing the color of a raised button is way too complex

See original GitHub issue

This is more a rant than a real issue really.

I used to have 4 colors for raised buttons: default - gray, primary - blue, secondary - green, contrast - red

Due to the recent change to the palette that removed the contrast color from I embarked into the task of making an additional custom red button and this is what I ended up with:

import Button, { ButtonClassKey, ButtonProps } from 'material-ui/Button';
import { fade } from 'material-ui/styles/colorManipulator';
import withStyles, { WithStyles } from 'material-ui/styles/withStyles';
import * as React from 'react';
import { errorColor, getContrastText, muiThemeNext } from '../../themes/muiThemeNext';

const styles = {
  raisedPrimary: {
    color: getContrastText(errorColor[600]),
    backgroundColor: errorColor[600],
    '&:hover': {
      backgroundColor: errorColor[800],
      // Reset on mouse devices
      '@media (hover: none)': {
        backgroundColor: errorColor[600],
      },
    },
  },

  flatPrimary: {
    color: errorColor.A400,
    '&:hover': {
      backgroundColor: fade(errorColor.A400, 0.12),
    },
  },
};

class DangerButtonWithStyles extends React.Component<ButtonProps & WithStyles<ButtonClassKey>> {
  render() {
    const classes = {
      ...this.props.classes,
      raisedPrimary: this.props.disabled ? undefined : this.props.classes.raisedPrimary,
      flatPrimary: this.props.disabled ? undefined : this.props.classes.flatPrimary,
    };

    return (
      <Button
        {...this.props}
        classes={classes}
        color="primary"        
      />    
    );
  }
}

export const DangerButton = withStyles(styles)(DangerButtonWithStyles) as React.ComponentType<ButtonProps>;

My first rant is about having to opt-in and out of classes depending on if the button is disabled or not. If I don’t do this then, since the generated classes have more priority than the default ones then the disabled styles are never applied. Wouldn’t it make more sense to make a deep merge of the current theme classes (including overrides) with the custom ones and then create class names based on that merge so that disabled overrides are not lost?

The second rant is about how overly complex this all seems. Couldn’t there be something like this instead?

<Button
  colorOverrides={{
    label:"blue", // label color for both raised and flat buttons
    background: "red" // background color for raised and flat buttons
    hoverBackground: "yellow" // background color on hover for raised and flat buttons
    disabledLabel: "blue" // label color for both raised and flat buttons when disabled
    disabledBackground: "red" // background color when disabled for raised and flat buttons
    ripple: "tomato" // ripple color?
  }}
> hi </Button>

each of those properties would default to undefined, meaning no customization applied and therefore the API is backwards compatible

There’s not so much of a problem with IconButton for example, since you can just use something like

export class DangerIconButton extends React.Component<IconButtonProps> {
  render() {
    return (
      <IconButton
        {...this.props}        
        color="inherit"
        style={{
          color: this.props.disabled ? undefined : errorColor.A400,
          ...this.props.style
        }}
      />    
    );
  }
}

still though it is funny how you have to be careful with not setting the color when disabled, so there could be something like icon, disabledIcon and ripple inside colorOverrides

And same thing about checkbox, etc.

Just my two cents 😄

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:52
  • Comments:20 (12 by maintainers)

github_iconTop GitHub Comments

5reactions
TidyIQcommented, Oct 11, 2019

I agree that changing button colors is too complex. I want to avoid nesting themes due to performance issues so I’ve made this custom button component, but I’m not sure if this would have even worse performance issues. I’d love some feedback on it.

It works fine and allows me to set any color for any button variants (i.e. either default, inherit, primaryLight, primary, primaryDark, secondaryLight, secondary, secondaryDark or any html color, such as #ff00ff for either outlined, text or contained buttons). I can also override the font color of the button if needed, and the hover no longer depends on what theme.palette.[primary/secondary].dark is - it’s calculated using fade or darken instead.

Here’s the code:

// ./Button/useColor.ts

import { useTheme } from "@material-ui/core/styles";

const useColor: UseColor = variant => {
  const theme = useTheme();

  switch (variant) {
    case "primaryLight": {
      return theme.palette.primary.light;
    }
    case "primary": {
      return theme.palette.primary.main;
    }
    case "primaryDark": {
      return theme.palette.primary.dark;
    }
    case "secondaryLight": {
      return theme.palette.secondary.light;
    }
    case "secondary": {
      return theme.palette.secondary.main;
    }
    case "secondaryDark": {
      return theme.palette.secondary.dark;
    }
    case "default":
    case "inherit": {
      return undefined;
    }
    default: {
      return variant;
    }
  }
};

export default useColor;

interface UseColor {
  (variant?: string): string | undefined;
}
// ./Button/index.tsx

import React, { FC } from "react";
import { fade, darken } from "@material-ui/core/styles/colorManipulator";
import MuiButton, {
  ButtonProps as MuiButtonProps
} from "@material-ui/core/Button";
import { makeStyles } from "@material-ui/core/styles";
import useColor from "./useColor";

const useStyles = makeStyles(theme => ({
  textPrimary: (props: StylesProps) => {
    if (props.bgColor) {
      const color = props.fontColor || props.bgColor;
      return {
        color,
        "&:hover": {
          backgroundColor: fade(
            props.bgColor,
            theme.palette.action.hoverOpacity
          )
        }
      };
    }
    return {};
  },
  containedPrimary: (props: StylesProps) => {
    if (props.bgColor) {
      const color =
        props.fontColor || theme.palette.getContrastText(props.bgColor);
      return {
        color,
        backgroundColor: props.bgColor,
        "&:hover": {
          backgroundColor: darken(props.bgColor, 0.25),
          // Reset on touch devices, it doesn't add specificity
          "@media (hover: none)": {
            backgroundColor: "transparent"
          }
        }
      };
    }
    return {};
  },
  outlinedPrimary: (props: StylesProps) => {
    if (props.bgColor) {
      const color = props.fontColor || props.bgColor;
      return {
        color,
        border: `1px solid ${fade(color, 0.5)}`,
        "&:hover": {
          backgroundColor: fade(
            props.bgColor,
            theme.palette.action.hoverOpacity
          ),
          border: `1px solid ${color}`
        }
      };
    }
    return {};
  }
}));

const Button: FC<ButtonProps> = ({
  children,
  color = "default",
  fontColor,
  ...props
}) => {
  const { variant } = props;
  const bgColor = useColor(color);
  const classes = useStyles({ bgColor, fontColor });

  let btnClasses: Record<string, string> | undefined;
  let btnColor: MuiButtonProps["color"];
  if (color !== "default" && color !== "inherit") {
    btnColor = "primary";
    switch (variant) {
      case "contained": {
        btnClasses = { containedPrimary: classes.containedPrimary };
        break;
      }
      case "outlined": {
        btnClasses = { outlinedPrimary: classes.outlinedPrimary };
        break;
      }
      case "text":
      case undefined: {
        btnClasses = { textPrimary: classes.textPrimary };
        break;
      }
      default:
        break;
    }
  } else {
    btnColor = color;
  }

  return (
    <MuiButton {...props} classes={btnClasses} color={btnColor}>
      {children}
    </MuiButton>
  );
};

export default Button;

export interface ButtonProps extends Omit<MuiButtonProps, "color"> {
  readonly color?: string;
  readonly fontColor?: string;
}

interface StylesProps {
  readonly bgColor: string | undefined;
  readonly fontColor: string | undefined;
}
2reactions
zsolt-devcommented, Apr 10, 2018

How come this is closed? There is still no way to change the color and this does not work:

const customTheme = outerTheme => ({
  ...outerTheme,
  palette: {
    ...outerTheme.palette,
    primary: red,
  },
});

Error: index.js:2178 Warning: Material-UI: missing color argument in fade(undefined, 0.12).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Customizing the color of a raised button is way too complex
This is more a rant than a real issue really. I used to have 4 colors for raised buttons: default - gray, primary...
Read more >
How to change the background color of a button dynamically ...
I think all you have to do is set the hightlightColor of the button and when it is pressed it will change to...
Read more >
Colour rule for multiple buttons in a complex platform
Issue: Since blue stands out more than teal I have selected teal as the secondary button, but the secondary button appears more often...
Read more >
New Buttons and Button Themes - Flutter documentation
Migrating buttons with custom disabled colors​​ This is a relatively rare customization. The FlatButton , RaisedButton , and OutlineButton classes have ...
Read more >
InDepth Guide for Customizing Angular Material Button
The very basic change we may need to font-color and background-color of buttons. And that, too with different states, like :hover , :focus...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found