TABLE OF CONTENTS
Styling with React’s Material-UI v4 – Part 2
Author Sagi Liba
Sagi Liba on Aug 19, 2021
12 min 🕐

Today, I will go over Material UI’s Theming. This will continue the first part where you’ve learnt how to style Material-UI’s components using makeStyles.

Prerequisite:

This article assumes you are familiar with styling components using makeStyles, which I’ve discussed at length in the previous article.

Styling with React’s Material-UI – Part 1

Material UI's Theming

Theming gives you the ability to set a baseline to style your entire application. By supplying all the relevant properties you will be able to reflect your product’s brand and style through Material-UI’s components.

Theming also gives you the ability to switch your styling at runtime, creating different themes for your application, such as light and dark themes.

Material-UI relies on React’s useContext, to provide a theming object throughout your application. By building upon useContext, Material-UI provides you with a ThemeProvider that should wrap your application’s root component, therefore making sure your entire application’s children will be able to consume the theme.

In the example below I am going to create a new theme object, make sure to wrap my entire application with the ThemeProvider, and use that theme to style a div element displaying the text: “I Read. You Learn.”.

Copy
import React from "react";
import { makeStyles, ThemeProvider } from "@material-ui/core/styles";
// Creating a basic theme object
// to hold our application styles.
const theme = {
text: {
color: "white",
background: "deepskyblue",
},
};
// Wrapping the entire application using the ThemeProvider,
// supplying it with the created theme object.
export default function App() {
return (
<ThemeProvider theme={theme}>
<ApplicationTree />
</ThemeProvider>
);
}
// Using the theme object to style our displayed text.
const useStyles = makeStyles((theme) => ({
textClass: {
background: theme.text.background,
color: theme.text.color,
},
}));
// Using the generated class from the
// returned makeStyles hook
// on our div element.
const ApplicationTree = () => {
const classes = useStyles();
return <div className={classes.textClass}>I Read. You Learn.</div>;
};

The result of the above example is:

Styling-with-Reacts-Material-UI-part-2

know, the styling is not that amazing but what’s important is that we have created a theming object that is provided to our entire application’s children components.

*  *  *

If you are going to view the theming object being provided, you will get the exact one we have created:

Copy
text: {
color: "white",
background: "deepskyblue"
}

Side-Note: the theme object used above will not be sufficient to style Material-UI components, that is because they rely on many other properties to function correctly. For the example shown above this theme object is enough.

Theme Material UI's Components

Now that we are familiar with the basics, let’s take a step further and theme some Material-UI components.

To be able to style Material-UI components effectively, we need to make sure our theming object has all the necessary properties.

To do so, we are going to use the createTheme function from Material-UI, it will enable us to set our application styles as expected, but it will also fill all the necessary properties to style Material-UI components with its default theme.

When you pass a theme object to createTheme it will perform a deep merge with Material-UI’s default theme, making sure you get the default theme + your styling changes (That is to make sure nothing is missing for our Material-UI components).

Side-Note: If you try to style Material-UI’s components without createTheme you will encounter errors, because you’ve probably missed some properties these components are relying on.

The deep merge will take your theme object and will override only the styles and properties at the exact location needed.

For example, passing the following theme object will override only the property main inside the palette primary color:

Copy
const theme = createTheme({
palette: {
primary: {
main: "red",
},
},
});
// Result:
{
// ...
// default properties
// ...
palette: {
primary: {
light: "#7986cb";
main: "red";
dark: "#303f9f";
contrastText: "#fff";
}
}
// ...
// other default properties
// ...
}

Instead of overriding the entire primary object, which will result in missing values:

Copy
// Missing "light","dark","contrastText"
{
// ...
// default properties
// ...
palette: {
primary: {
main: "red";
}
}
// ...
// other default properties
// ...
}
*  *  *

Let’s start with a basic example:

Copy
const theme = createTheme({});

I’ve passed an empty theme object to createTheme, createTheme will then generate our new theme object, it will contain all our default Material-UI styling:

Copy
{
breakpoints: Object
direction: "ltr"
mixins: Object
overrides: Object
palette: Object
props: Object
shadows: Array(25)
typography: Object
spacing: ƒ spacing() {}
shape: Object
transitions: Object
zIndex: Object
}

As you can see createTheme has generated a theme object that holds all the necessary properties for the Material-UI components to behave correctly.

Side-Note: If you are not using the ThemeProvider, you will receive the default theme that comes with Material-UI automatically, currently it will look the same as the above object, because we have not entered any styles yet. You can explore it further here: Default Theme

Now that you know how createTheme works we will start by styling a Material-UI Button component.

The "Overrides" Property

To make sure our styles take effect we have to use the overrides property, which enables us to override any style we want for Material-UI components.

The following button will use the primary color being set by the default theme:

Copy
const ApplicationTree = () => {
return (
<Button variant="contained" color="primary">
I Read. You Learn
</Button>
);
};

Styling-with-Reacts-Material-UI-part-2-Button-Before

We can then use the ThemeProvider to inject a custom theme to override the primary color for all the Buttons in our application, and set it to “red”.

Copy
const theme = createTheme({
// Using the "overrides" property
// To override any style we want.
overrides: {
MuiButton: {
containedPrimary: {
backgroundColor: "red",
},
},
},
});
export default function App() {
return (
<ThemeProvider theme={theme}>
<ApplicationTree />
</ThemeProvider>
);
}

As discussed in the previous article, you can find all the relevant rule names, such as “MuiButton” and “containedPrimary” in the component API, for example here is the Button API: Button Docs API

Styling-with-Reacts-Material-UI-part-2-Button-After

*  *  *
Useful Note: As noted in the previous article you should style your components using makeStyles, but you can also style your components using a theme that will wrap a specific component, instead of wrapping the entire application and applying that style to the entire components tree.

If you are having trouble accessing nested child components, and you have not used makeStyles to style your component, then as a last resort you can access the classes you need by using regular SASS/CSS DOM classes, because the class names have not been suffixed with an identifier such as “MuiButton-root-16“,and the class name will be “MuiButton-root“.

Working With Nested Theme Providers

Let’s say we set a theme for our application, and at some nested child component you have found the need to style it using another theme object, you can either decide to override the outer theme completely or decide to extend it.

To access the outer theme, pass a function to the theme property inside the ThemeProvider, It will be automatically injected by Material-UI with the outer theme.

Copy
// Accessing the outerTheme by passing a function.
<ThemeProvider theme={(outerTheme) => outerTheme}>
<DeeplyNestedButton />
</ThemeProvider>

Side-Note: The passed function must return a valid theme object. To make sure its valid you can use the createTheme function.

Let’s start by creating an application wide theme called applicationTheme, it will be behave as our outer theme.

Copy
const applicationTheme = createTheme({
overrides: {
MuiButton: {
containedPrimary: {
backgroundColor: "red",
color: "yellow",
},
},
MuiFormLabel: {
root: {
color: "black",
},
},
},
});

Then I am going to pass the theme to the ThemeProvider, wrapping our entire application, as already seen before:

Copy
export default function App() {
return (
<ThemeProvider theme={applicationTheme}>
<ApplicationTree />
</ThemeProvider>
);
}

Now I am going to create two buttons, one will be the same as before, the other will mimic a deeply nested component that we wish to style with a custom theme:

Copy
// We wish to style this Material-UI
// Button with another theme.
const DeeplyNestedButton = () => {
return (
<Button variant="contained" color="primary">
Deeply Nested Button
</Button>
);
};
const ApplicationTree = () => {
return (
<>
<Button variant="contained" color="primary">
I Read. You Learn
</Button>
<br />
<br />
<DeeplyNestedButton />
</>
);
};

You will then see two buttons, with a red background and a yellow text as expected, because our application theme affects all the buttons styles.

Styling-with-Reacts-Material-UI-part-2-Nested-Themes-1

Now I want to add a custom theme to my button, that will affect only the “Deeply Nested Button”, and change its colors.

To do so we need to pass a new theme object to the ThemeProvider wrapping our “Deeply Nested Button”:

Copy
// Our new nested theme
const nestedTheme = createTheme({
overrides: {
MuiButton: {
containedPrimary: {
backgroundColor: "#98c379", // green
color: "yellow",
},
},
},
});
const ApplicationTree = () => {
return (
<>
<Button variant="contained" color="primary">
I Read. You Learn
</Button>
<br />
<br />
{/* Passing the nested theme here*/}
<ThemeProvider theme={nestedTheme}>
<DeeplyNestedButton />
</ThemeProvider>
</>
);
};

The downside is that we will lose all of the styles we have from our parent applicationTheme, this means other styled components such as MuiFormLabel that I’ve set earlier will lose their styling in case they are a child of our new ThemeProvider that received the nestedTheme.

Copy
// Missing the MuiFormLabel!
overrides: {
MuiButton: {
containedPrimary: {
backgroundColor: "#98c379";
color: "yellow";
}
}
}

If it fits your use case then great, but if you need to preserve the applicationTheme styles, then you need to properly extend it.

To properly extend a theme you will need to use the createTheme second argument, which receives another theme object to deeply merge with:

Copy
const ApplicationTree = () => {
return (
<>
... ...
<ThemeProvider
theme={(outerTheme) => createTheme(outerTheme, nestedTheme)}
>
<DeeplyNestedButton />
</ThemeProvider>
... ..
</>
);
};

The result would look like this:

Styling-with-Reacts-Material-UI-part-2-Nested-Themes-3

And the theme properties will be extended correctly:

Copy
overrides: {
MuiButton: {
containedPrimary: {
backgroundColor: "#98c379"
color: "yellow"
}
},
MuiFormLabel {
root {
text: "black"
color: "yellow"
}
}
}

Be careful not to override the theme carelessly like the next code snippet, it will cause you to unintentionally remove the MuiFormLabel styles, and any other styles you might have set, that do not exists in the nestedTheme.

Copy
const ApplicationTree = () => {
return (
<>
... ...
<ThemeProvider
theme={(outerTheme) => createTheme({ ...outerTheme, nestedTheme })}
>
<DeeplyNestedButton />
</ThemeProvider>
... ...
</>
);
};

Consuming Theme

There will be many times where you will need to access your theme directly, instead of only accessing it inside makeStyles.

As I’ve mentioned here before, Material-UI’s Theme is based upon React’s useContext, therefore you can use the useTheme hook that will enable you to access the theme directly.

Usage:

Copy
const DeeplyNestedButton = () => {
// Directly accessing the theme,
// in this case the nestedTheme dissussed before,
const theme = useTheme();
return (
<Button variant="contained" color="primary">
Deeply Nested Button
</Button>
);
};

You can now use whatever values you need inside your functional components.

Overriding Default props

Another useful use case for using a theme, is to set default props for your entire application or even for a specific type of component.

Lets say you would like to disable the ripple effect for all the Buttons in your application.

You can easily do so by using the theme’s props property, and specifying that you wish to disable the ripple effect:

Copy
const applicationTheme = createTheme({
props: {
// Component Name - Search the Component API
MuiButtonBase: {
// Default prop to change
disableRipple: true,
},
},
});
*  *  *

You’ve made it!

This was another lengthy article, but I’m sure you are now able to theme your application with confidence.

© 2020-present Sagi Liba. All Rights Reserved