22

I'm currently customizing a few components using global theme overrides in the hopes of maintaining as much of the integrity of the Material-UI theming engine as possible. I know I could accomplish what I'm trying to do using composition, but I want to see if it's possible to achieve this via overrides.

The Goal

Change the background color of the BottomNavigation component to use the primary color of the current theme, and ensure the label gets a color that is legible on top of that background color.

My Current Approach

const theme = createMuiTheme({
    palette: {
      primary: {
        main: 'rgba(217,102,102,1)'
      }
    },
    overrides: {
        MuiBottomNavigation: {
            root: {
                backgroundColor: 'rgba(217,102,102,1)'
            }
        },
        MuiBottomNavigationAction: {
            wrapper: {
                color: '#fff'
            }
        }
    }
});

This code accomplishes the task and turns the bottom navigation red and the label/icons white. However, I want the flexibility of being able to change the primary color in the palette and have the component update accordingly.

What I'm Trying To Do

const theme = createMuiTheme({
    palette: {
      primary: {
        main: 'rgba(217,102,102,1)'
      }
    },
    overrides: {
        MuiBottomNavigation: {
            root: {
                backgroundColor: 'primary.main'
            }
        },
        MuiBottomNavigationAction: {
            wrapper: {
                color: 'primary.contrastText'
            }
        }
    }
});

In this way I could easily update the primary color and not have to worry about changing every reference to it across my overrides. I realize I could extract the rgba value out into a const and that would accomplish part of my goal, but I don't see how I could access something as useful as contrastText in case I choose a much lighter primary color.

So - does anyone know of a way to reference theme palette colors in a theme override definition? Any help would be greatly appreciated!

n-devr
  • 450
  • 1
  • 3
  • 10

4 Answers4

23

There's another approach here. createMuiTheme accepts any number of additional theme objects to be merged together.

With that in mind you could replicate your accepted answer without having two different ThemeProvider. And if you move the theme definition to its own module, it won't be recreated on each render.

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

const globalTheme = createMuiTheme({
  palette: {
    primary: {
      main: "rgba(217,255,102,1)"
    }
  }
});

const theme = createMuiTheme(
  {
    overrides: {
      MuiButton: {
        root: {
          backgroundColor: globalTheme.palette.primary.main
        },
        label: {
          color: globalTheme.palette.primary.contrastText
        }
      }
    }
  },
  globalTheme
);

export default theme;

I updated the CodeSandBox to reflect this.

Nicolás Fantone
  • 2,055
  • 1
  • 18
  • 24
18

Ill provide two solutions- one is more readable and maintainable, and one has better performance.

  1. The readable and maintainable approach:
    Create nested themes.
    One theme will be for defining the palette, and one theme will be for overrides.
    Because its two themes, you can access the palette theme from overrides theme:

    const globalTheme = createMuiTheme({
      palette: {
        primary: {
          main: 'rgba(217,255,102,1)'
        }
      },
    });
    
    const overridesTheme = createMuiTheme({
      overrides: {
        MuiButton: {
            root: {
                backgroundColor: globalTheme.palette.primary.main,
            },
            label: {
              color:globalTheme.palette.primary.contrastText,
            }
        },
    }
    })
    

    You can refer to this CodeSandbox

    This approach doesn't have good performance, bacause every render a new CSS object will be computed and injected


  1. The better performance approach:
    First you create an Mui theme skeleton, with the palette. After it has been created, you add the styles that rely on the palette (notice how I have to use the spread operator a lot to avoid deleting styles):

    const theme = createMuiTheme({
      palette: {
        primary: {
          main: 'rgba(217,255,102,1)'
        }
      },
    })
    
    theme.overrides = {
      ...theme.overrides,
      MuiButton: {
          ...theme.MuiButton,
          root: {
             ...theme.root,
              backgroundColor: theme.palette.primary.main,
          },
          label: {
            ...theme.label,
            color:theme.palette.primary.contrastText,
          }
      },
    }
    

    You can refer to this CodeSandbox

Ido
  • 5,363
  • 2
  • 21
  • 30
  • 1
    Thank you! I can't believe I didn't think to just edit the theme object after it's created...learning React and MUI at the same time is definitely confusing at times. I implemented the second option and it worked perfectly. I am curious to understand one point though - could you elaborate a bit about why the first solution would cause a new CSS Object to be computed/injected while the second one would not? – n-devr Aug 23 '19 at 19:56
  • 1
    Yes. The injected css is cached with theme. If you provide a new theme at each render, the cache is not longer valid, so a new CSS object will be computed and injected. And by nesting themes you do that- every render you provide a new theme. – Ido Aug 23 '19 at 22:54
  • You could always use `useMemo` to ensure the theme doesn't get re-created - I currently do this as my theme can be recreated based on the user's preference of dark/light mode. Question on the first option - does the `overridesTheme` inherit from the `globalTheme`? i.e would the palette on the `overridesTheme` contain the modified primary colour? Or would it only have it referenced in the overrides? – Bill May 06 '20 at 13:58
  • 1
    @n-devr There's an even better approach to this. See my new answer below. – Nicolás Fantone Jun 18 '20 at 15:18
9

There's a new way to do it in MUI v5 that is much more straightforward. Suppose you want to override the background color of an Mui Button. Inside the custom theme object, you specify this:

const customTheme = createTheme({
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          backgroundColor: 'red',
        },
      },
    },
  },
});

This is the usual case for hard coding a value. Now, if you wanted to use some property like the primary color from the custom theme you just made, you can pass an arrow function to the root(or whatever component you need to override) with an object as the argument, and return an object containing the styles you need. You can access the theme inside the returned object.

const customTheme = createTheme({
  palette: {
    primary: {
      main: '#002255',
    },
  },
  components: {
    MuiButton: {
      styleOverrides: {
        root: ({ theme }) => ({
          backgroundColor: theme.palette.primary.main,
        }),
      },
    },
});

As you can see, inside the object you can destructure the theme. Similarly you can destructure ownerState, which contains all the props and state of the component, which you can access using dot operator. To illustrate, I have declared a prop called dark on an Mui Button Component

<Button dark={true}>Random Button</Button>

Now I can access this prop in the button, using the object destructuring

const customTheme = createTheme({
  palette: {
    primary: {
      main: '#002255',
    },
  },
  components: {
    MuiButton: {
      styleOverrides: {
        root: ({ ownerState, theme }) => ({
          backgroundColor: ownerState.dark
            ? theme.palette.primary.dark
            : theme.palette.primary.light,
        }),
      },
    },
});
sayandcode
  • 1,775
  • 1
  • 11
  • 24
  • 1
    Do you know how to make this work in TS? After adding the dark property to my button it works for a brief second before the compiler throws an error that `No overload matches this call.` – RogerKint Oct 13 '22 at 15:24
  • In my case `ownerState.dark` was `theme.palette.mode.dark` – James Gentes Mar 04 '23 at 16:16
1

For people looking at this for answers on theming and switching between themes.

https://codesandbox.io/s/material-theme-switching-with-pallete-colors-vfdhn

Create two Theme objects and switch between them. Use the same theme property between them so all your overrides can use the same palette to ensure things are not repeated and we use the overrides completely.

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

let globalTheme = createMuiTheme({
  palette: {
    primary: {
      main: "#fa4616"
    }
  }
});

export const LightTheme = createMuiTheme(
  {
    overrides: {
      MuiButton: {
        root: {
          backgroundColor: globalTheme.palette.primary.main
        },
        label: {
          color: globalTheme.palette.primary.contrastText
        }
      }
    }
  },
  globalTheme
);

globalTheme = createMuiTheme({
  palette: {
    primary: {
      main: "#0067df"
    }
  }
});

export const DarkTheme = createMuiTheme(
  {
    overrides: {
      MuiButton: {
        root: {
          backgroundColor: globalTheme.palette.primary.main
        },
        label: {
          color: globalTheme.palette.primary.contrastText
        }
      }
    }
  },
  globalTheme
);
C B
  • 1,964
  • 14
  • 13
  • I'm trying to customize light/dark themes, and while this comes closer my goal, swapping the whole theme at the top level seems to forego MUI's built-in `palette.mode` toggle. Posted a separate SO on this [here](https://stackoverflow.com/questions/68862138/extending-material-uis-existing-dark-mode-colors) – paws Aug 20 '21 at 13:42