Creating custom variants with Material-UI

Viewed 12967

I am trying to create custom variants for the Button component in Material-UI.

My first step is to create a component based off of the Button component with the styles that I want:

// CTA.js

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

const useStyles = makeStyles({
  root: { // CUSTOM__STYLES },
  label: { // CUSTOM__STYLES },
});

const CTA = ({ children }) => {
  const classes = useStyles();

  return (
    <Button
      classes={{
        root: classes.root, 
        label: classes.label,
      }}
    >
      {children}
    </Button>
  );
};

I then import that component into a new Button component that I am creating as follows:

// Button.js
import MuiButton from "@material-ui/core/Button";
import CTA from "./CTA";

const Button = ({ variant, ...muiButtonProps }) => {
  if (variant === "cta") {
    return <CTA {...muiButtonProps} />;
  }
  return <MuiButton {...muiButtonProps} />;
};

Now, what I want is to be able to import my new Button component and have it work just like a regular Material-UI button component, but with the addition of variant="cta". However, it does not quite work.

For example, take a look at the following:

// Header.js
import { Button as MuiButton } from "@material-ui/core";
import { Button } from "@/theme/button.js";

...

<MuiButton variant="outlined">Mui Button</MuiButton>  // works
<Button variant="outlined">Button</Button> // does not work
<Button variant="cta">CTA Button</Button>  // works

I see that my new custom Button component works when I use variant="cta", but it does not work when I use any of the built-in Material-UI variant options. I cannot figure out what that is. I would have thought that <Button variant="outlined"> would work just like <MuiButton variant="outlined">. But that is not the case.

Any idea why not and how to fix it?

4 Answers

In Material-UI v5, you can easily create a new variants for your components (See the list of supported components in this RFC) using createTheme() which removes the need to create wrapper components. Below is the minimum example:

const theme = createTheme({
  components: {
    MuiButton: {
      variants: [
        {
          props: { variant: 'dashed' },
          style: {
            textTransform: 'none',
            border: `2px dashed grey${blue[500]}`,
          },
        },
      ],
    },
  },
});
export default function Demo() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="outlined">
        Outline
      </Button>
      <Button variant="dashed">
        Dashed
      </Button>
    </ThemeProvider>
  );
}

For typescript users, you also need to update the variant definition using module augmentation.

declare module '@mui/material/Button' {
  interface ButtonPropsVariantOverrides {
    dashed: true;
  }
}

Live Demo

Edit GlobalThemeVariants Material Demo

Two options are possible, it dont work cause you destruct "variant" and don't forward it to your MuiButton.

You can do it this way in your Button.js

const Button = ({ variant, ...muiButtonProps }) => {
  if (variant === "cta") {
    return <CTA {...muiButtonProps} />;
  }
  return <MuiButton variant={variant} {...muiButtonProps} />;
};

or

const Button = (muiButtonProps) => {
  if (muiButtonProps.variant === "cta") {
    return <CTA {...muiButtonProps} />;
  }
  return <MuiButton {...muiButtonProps} />;
};

Take a look at the docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

// Stage 4(finished) proposal
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

In case, you are trying to style a given mui component globaly with the version 5 , it should be something like:

 const theme = createTheme({
 components:{
    MuiTableCell:{
      styleOverrides:{
        root:{
          color: "red"
        }
      }
    }
  },
});

to add on to NearHuscarl's answer if you want to customize a component that doesn't support custom variants yet, you can still change the style based on prop. Same as in Yassine CHABLI's answer, but one level more. I had to do this recently when I wanted to be able to past variant='largeText' to my MuiTooltips.

You're able to enter functions as the styleOveride and its passed an object with ownerState that has all the props you passed in at the top level.

From these docs: https://mui.com/material-ui/customization/theme-components/

Minimal version looks like this:

const theme = createTheme({
  components: {
    MuiTooltip: {
      styleOverrides: {
        tooltip: ({ ownerState }) => {
          if (ownerState?.variant === 'largeText') {
            return { fontSize: 16 };
          }
          return {};
        },
      },
    },
  },
});

Related