Designing unique variations using Material UI

I am currently exploring the creation of custom variants for the Button component in Material UI.

To start off, I am developing a customized component based on the Button component with specific styles:

// 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>
  );
};

Subsequently, I import this component into a new rendition of the Button component that I am constructing as shown below:

// 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} />;
};

The goal is to be able to utilize my newly created Button component just like a standard Material-UI button component but with an additional variant="cta". However, it seems to be experiencing some hurdles.

For instance, refer to the following scenario:

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

...

<MuiButton variant="outlined">Mui Button</MuiButton>  // functions correctly
<Button variant="outlined">Button</Button> // does not function as intended
<Button variant="cta">CTA Button</Button>  // works fine

While my personalized Button component operates with variant="cta", it fails to do so when employing any of the predefined Material-UI variant options. The reason behind this discrepancy eludes me. I assumed that

<Button variant="outlined">
would behave similarly to
<MuiButton variant="outlined">
. However, this is not the case.

Any insights on why this may be happening and how to rectify it?

Answer №1

In the latest version of Material-UI (v5), you have the ability to effortlessly create custom variants for your components. This eliminates the need for creating wrapper components, making the process much simpler. Check out the list of supported components in this RFC. See below for a basic 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>
  );
}

If you are using typescript, make sure to update the variant definition through module augmentation.

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

Live Demo

https://codesandbox.io/s/2b8sh?file=/demo.js

Answer №2

There are two possible options for solving this issue: you may find that it is not working because you are destructing the "variant" without forwarding it to your MuiButton component.

To address this problem, consider implementing the following changes in your Button.js file:

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

Alternatively, you can also try this approach:

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

For more information on destructuring assignments in JavaScript, refer to the documentation at: 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}

Answer №3

If you're looking to customize a specific mui component globally using version 5, the code snippet would look similar to this:

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

Answer №4

Expanding on the previous responses from NearHuscarl and Yassine CHABLI, if you need to customize a component that doesn't currently support custom variants, you can still modify the style based on a prop value. I recently encountered this situation when I wanted to apply the variant 'largeText' to my MuiTooltips.

You have the option to utilize functions as the styleOverride, which receives an object containing ownerState with all the props passed at the top level.

For more information, refer to these resources: https://mui.com/material-ui/customization/theme-components/

A simplified version of the code snippet is shown below:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error encountered while making an http get request for a node that returns JSON

I've been struggling with this issue for quite some time now. While I've come across similar problems on Stack Overflow, none of the suggested solutions seem to work for me. I keep encountering the following error message: undefined:1 SyntaxErro ...

Changing characters to asterisks using Javascript

I am currently working on a function that transforms all characters after the first word into asterisks. For example, if I have MYFIRSTWORD MYSECONDWORD, I would like it to show as: MYFIRSTWORD *** Currently, I'm using the code below which replaces ...

What is the process for adjusting the form transition?

I am currently working on a form that has a transition effect However, I encountered an issue: check out the problem here My goal is to keep the magnifying glass fixed in place while the form moves Here is a snippet of my code: import Search fro ...

Leveraging a VueJS prop as a variable in an array mapping operation

Trying to figure out a solution where a variable (prop) can be used in an array map function. The initial code snippet looks like this: var result = this.$store.getters['example/store'].map(a => a.fixed_column) I aim for fixed_column to be ...

Here is a helpful guide on updating dropdown values in real time by retrieving data from an SQL database

This feature allows users to select a package category from a dropdown menu. For example, selecting "Unifi" will display only Unifi packages, while selecting "Streamyx" will show only Streamyx packages. However, if I first select Unifi and then change to S ...

Is there a way to retrieve data from my JSON file located in the public folder within Next.js 13.3?

My current challenge involves retrieving JSON data from the public folder. async function fetchData() { try { const response = await fetch('/data.json'); const jsonData = await response.json(); return jsonData; } catch (error) { ...

The startAdornment MUI ensures that the label is consistently displayed at the top

When using Input with startAdornment, I noticed that the label always appears on top. However, I want the label to be on the same line as the icon when the input is empty and then move on top when the user starts typing. Is there a way to achieve this? ...

Which file from Next.js should I statically serve using Node?

Whenever I work with React, my standard process includes running npm build, moving the content to a directory named public in Node, and then including the following code snippets: node/app.js app.use(express.static(path.join(__dirname, 'public') ...

Concealing axis lines within the initial circular grid or opting not to include them

Is there a way to incorporate some whitespace within the center circle of the radar chart? I'm aiming for the axis to commence at 1 radius (the initial circular line) or perhaps have the stoke set to 0 for the primary radius. Any assistance would be g ...

Is there a way to access an SD card by clicking on an HTML link using JavaScript?

UPDATE: I am seeking a way to embed an HTML file with JavaScript or jQuery that can directly access the contents of the SD card while being opened in a browser. Currently, I have posted code for accessing it through an activity, but I want to be able to d ...

Monitor Socket IO for client disconnection events

I am facing an issue where I need to identify when a user loses connection to the socket. It seems that socket.on("disconnect") is not triggering when I simply close my laptop, leading to the ajax call not executing to update the database and mark the us ...

Node.js Express not inserting data with Mongoose when using form data

For the past two weeks, I have been struggling to post data to insert into a database using form-data. It consistently shows a 400 bad request error. Below is my code for server.js: require('./db.js') let express = require('express') ...

Selecting radio button does not update corresponding label

I am having an issue with setting a radio button as checked. In the example snippet, it works perfectly fine but on my localhost, it is not working. Even though the input gets checked, the label does not change. Surprisingly, if I set another radio button ...

Styled-Component: Incorporating Variables into Styled-Component is my goal

Currently, I am working on an app and have created a separate file for styling. I decided to use style-components for custom CSS, but faced an issue where I couldn't access variables instead of HEX values. Even after storing color values in a variable ...

Error message: Unable to render content from layout.js in index.js due to unspecified

I am working with Layout.js and this is the code snippet: import Layout from '../components/Layout'; export default function Home(){ <Layout> test </Layout> } I am trying to display 'test' in m ...

Prompt for confirmation in ASP.NET code-behind with conditions

I've searched around for a solution to this problem. Below is a representation of my pseudocode: bool hasData = ItemHasData(itemid); Confirm = "false"; // hidden variable if (hasData) { //Code to call confirm(message) returns "true" or "false" ...

The specified function is not recognized within the HTMLButtonElement's onclick event in Angular 4

Recently diving into Angular and facing a perplexing issue: "openClose is not defined at HTMLButtonElement.onclick (index:13)" Even after scouring through various resources, the error seems to be rooted in the index page rather than within any of the app ...

What is the best way to stylize a date using Bootstrap-datepicker?

While this topic is well-known, I have a slightly more complex question regarding it. I have gone through the documentation here. My goal is to display the date in French format (dd/mm/yyyy) and save the value in US format (yyyy-mm-dd). Additionally, I nee ...

Sending data from a React client to an Express server using a POST

Currently facing a challenge with sending a post request from React to my Express server backend. The request payload seems to be correctly structured, and I can successfully receive a hardcoded response from the server on the frontend. However, the issue ...

Can an AJAX upload progress bar be implemented in Internet Explorer without using Flash?

I've been searching for solutions to upload files without using flash, but all of them either require flash or lack a progress bar on IE (7-8). I couldn't find any mention of an "progress" event in the MSDN documentation for XMLHTTPRequest. Is i ...