Muhi Masri

Customizing Theme, Palette, and Colors with Material UI (MUI)

A guide to creating a custom theme, colors, and palettes with React and MUI

Last updated on January 20, 2023

react
mui
Material Colors

Palettes and themes are essential parts of the overall UI look and feel. They convey meaning and consistency throughout the application.

This article will focus on creating and customizing colors and palettes using MUI theme. There are three important parts that we need to understand:

  • Customizing MUI theme
  • Adding a custom palette
  • Using custom colors

Before we start, below is the complete working code example:

If this is your first time working with MUI, please follow the installation instructions from the official documentation to get started.

Custom MUI theme

Creating a custom theme is typically done at the root or main app section to be accessible to all components. We can use ThemeProvider as a root app component and pass in the customized theme.

import { ThemeProvider, createTheme } from "@mui/material/styles";
const theme = createTheme({
  // Override or create new styles, colors, palettes...
});
export default function App() {
  return (
    <ThemeProvider theme={theme}>
      // MUI components
    </ThemeProvider>
  );
}

Let’s look deeper at why and how to use custom themes.

Why is it important?

Let’s assume we have a header with some input elements on the screen and want to change the button’s primary color.

Default Theme

import {
  Button,
  Box,
  AppBar,
  Toolbar,
  Typography,
  Checkbox,
  FormControl,
  FormControlLabel,
  RadioGroup,
  Radio,
  Stack
} from "@mui/material";

export default function App() {
  return (
    <Box>
      <Box sx={{ flexGrow: 1 }}>
        <AppBar position="static">
          <Toolbar>
            <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              Header
            </Typography>
            <Button color="inherit">Login</Button>
          </Toolbar>
        </AppBar>
      </Box>
      <Stack gap={2} sx={{ p: 2 }} alignItems="flex-start">
        <Button variant="contained">
          Button
        </Button>
        <Box>
          <Checkbox defaultChecked />
        </Box>
        <Stack direction="row">
          <FormControl>
            <RadioGroup
              aria-labelledby="demo-radio-buttons-group-label"
              defaultValue="optionA"
              name="radio-buttons-group"
            >
              <FormControlLabel
                value="optionA"
                control={<Radio />}
                label="Option A"
              />
              <FormControlLabel
                value="optionB"
                control={<Radio />}
                label="Option B"
              />
            </RadioGroup>
          </FormControl>
        </Stack>
      </Stack>
    </Box>
  );
}

Adding a new background color using sx can be persuasively an easy and quick solution:

<Button sx={{ bgcolor: "#2a9461" }}>
  Button
</Button>

But the problem arises when the primary color is used in other elements. Remember that we aren’t just changing the background color, we are also changing the primary palette color. So with the above solution, we need to apply the exact change to all other elements, such as the checkbox, radio buttons, and headers.

Below, we can see how the change only affected the button but not the rest.

Default Theme

This approach is redundant and hard to scale as the application grows bigger and bigger. For that reason, MUI provides a ThemeProvider component to inject a theme into our application.

How to use it

Let’s create a new custom theme in the main app component to override the primary color and then pass it into ThemeProvider.

import { ThemeProvider, createTheme } from "@mui/material/styles";

const theme = createTheme({
  palette: {
    primary: {
      main: "#2a9461"
    }
  }
});

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      // MUI components
    </ThemeProvider>
  );
}

Here is how it looks after creating a custom theme:

Style Tokens

Similarly, we can customize other palettes like the secondary color.

import { ThemeProvider, createTheme } from "@mui/material/styles";

const theme = createTheme({
  palette: {
    primary: {
      main: "#2a9461"
    },
    secondary: {
      main: "#494c7d"
    }
  }
});

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      // MUI components
    </ThemeProvider>
  );
}

We have to set the color value to use a secondary color. If not specified, the default is primary.

Let’s change the header and button to a secondary color.

export default function App() {
  return (
    <Box>
      <Box sx={{ flexGrow: 1 }}>
        <AppBar position="static" color="secondary">
          <Toolbar>
            <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              Header
            </Typography>
            <Button color="inherit">Login</Button>
          </Toolbar>
        </AppBar>
      </Box>
      <Stack gap={2} sx={{ p: 2 }} alignItems="flex-start">
        <Button variant="contained" color="secondary">
          Button
        </Button>
        <Box>
          <Checkbox defaultChecked />
        </Box>
        <Stack direction="row">
          <FormControl>
            <RadioGroup
              aria-labelledby="demo-radio-buttons-group-label"
              defaultValue="optionA"
              name="radio-buttons-group"
            >
              <FormControlLabel
                value="optionA"
                control={<Radio />}
                label="Option A"
              />
              <FormControlLabel
                value="optionB"
                control={<Radioo />}
                label="Option B"
              />
            </RadioGroup>
          </FormControl>
        </Stack>
      </Stack>
    </Box>
  );
}

Custom Theme

We can see how “primary” and “secondary” now reflects the new colors (green and purple).

Custom palette

How it works

The default theme has several built-in palettes, such as primary, secondary, success, and warning.

Style Tokens

And as shown above, each palette has four values (main, dark, light and contrastText).

When styling components using sx, we can directly access values as follows:

<Button sx={{ color: "primary.main" }}>Button</Button>

Which is a shortcut to doing this:

<Button sx={{ color: (theme) => theme.palette.primary.main }}>
  Button
</Button>

Cool right! Now, when using the color prop to set a palette, MUI will automatically pick “main” as the main color and “contrastText” as the text color.

<Button variant="contained" color="secondary">
  Button
</Button>

Secondary Button

Create a new palette

We can append a new color to the palette object with a main and text color to create a new custom palette.

const theme = createTheme({
  palette: {
    primary: {
      main: "#2a9461"
    },
    secondary: {
      main: "#494c7d"
    },
    primaryLight: {
      main: "#dbece2",
      contrastText: "#616161"
    }
  }
});

“contrastText”, “dark” and “light” are optional, and if omitted, their values will be calculated automatically according to the “tonalOffset” and “contrastThreshold” of the “main” value. More details on this are available on MUI Palette Documentation

Let’s replace the header color with the new custom palette we just created.

export default function App() {
  return (
    <Box>
      <Box sx={{ flexGrow: 1 }}>
        <AppBar position="static" color="primaryLight">
          <Toolbar>
            <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              Header
            </Typography>
            <Button color="inherit">Login</Button>
          </Toolbar>
        </AppBar>
      </Box>
      <Stack gap={2} sx={{ p: 2 }} alignItems="flex-start">
        <Button variant="contained" color="secondary">
          Button
        </Button>
        <Box>
          <Checkbox defaultChecked />
        </Box>
        <Stack direction="row">
          <FormControl>
            <RadioGroup
              aria-labelledby="demo-radio-buttons-group-label"
              defaultValue="optionA"
              name="radio-buttons-group"
            >
              <FormControlLabel
                value="optionA"
                control={<Radio />}
                label="Option A"
              />
              <FormControlLabel
                value="optionB"
                control={<Radioo />}
                label="Option B"
              />
            </RadioGroup>
          </FormControl>
        </Stack>
      </Stack>
    </Box>
  );
}

Custom Theme

We can see above how the header reflects the new text and background color.

One last detail, if we’re using typescript, we also need to use module augmentation for the theme to accept the new values.

declare module '@mui/material/styles' {
  interface Palette {
    primaryLight: Palette['primary'];
  }

  interface PaletteOptions {
    primaryLight: PaletteOptions['primary'];
  }
}

Custom colors

MUI theme provides a set of built-in colors that we can import and use anywhere in the application. Each set of colors comes with all shades from 0 to 100.

Let’s import the theme colors and replace the values we had earlier.

import { createTheme } from '@mui/material/styles';
import { green, purple } from '@mui/material/colors';

const theme = createTheme({
  palette: {
    primary: {
      main: green[500],
    },
    secondary: {
      main: purple[500],
    },
  },
});

If we look into the source code, we can see all the provided shades.

Green palette

Great, but there is a caveat, not every green palette is the same. The one MUI provides won’t work with the new primary and secondary colors we created, so we need to add our own custom colors and shades.

Usually, the designer provides us with all the necessary shades. But for now, we can use this helpful tool to autogenerate it from the primary and secondary hex values we added in the first section (#2a9461 and #494c7d).

Let’s create a new color.js file that exports all the new shades we just generated:

export const green = {
  50: "#dbece2",
  100: "#a7d0b8",
  200: "#6eb18b",
  300: "#2a9461",
  400: "#008044",
  500: "#006c27",
  600: "#006020",
  700: "#005116",
  800: "#00410b",
  900: "#002700"
};

export const purple = {
  50: "#e9eaf0",
  100: "#c7cada",
  200: "#a3a8c1",
  300: "#8186a7",
  400: "#686c95",
  500: "#505485",
  600: "#494c7d",
  700: "#414371",
  800: "#393964",
  900: "#2d2a4c"
};

Now, we can import the new colors and use them in the custom theme:

import { green, purple } from "./colors";
import { ThemeProvider, createTheme } from "@mui/material/styles";

const theme = createTheme({
  palette: {
    primary: {
      main: green[300]
    },
    secondary: {
      main: purple[600]
    },
    primaryLight: {
      main: green[50]
    }
  }
});

Summary

Having a theme brings consistency and minimizes redundancies when styling components. In this article, we learned how to override MUI theme, add a new custom palette, and use custom colors and shades.

Lastly, understanding MUI’s system is essential for maximizing usability and enhancing the developer’s experience.

I hope you enjoyed learning from this article and if you have any questions, please leave a comment below.

Bye for now!

Recommended Reading

Discover different solutions for customizing a disabled button with Material UI and React

react
mui

Discussion

Master your front-end development techniques 🌟

Subscribe to stay up-to-date and receive quality front-end development articles straight to your inbox!

No spam, sales, or ads. Unsubscribe anytime you wish.

© 2023, Muhi Masri