TABLE OF CONTENTS

Styling with React’s Material-UI v4 – Part 1

Author Sagi Liba
Sagi Liba on Aug 5, 2021
16 min 🕐

For the past year I’ve been working with Material-UI, and let’s say my relationship with it had its ups and downs. I’ve finally found the time to tackle Material-UI heads-on and truly understand how it works.

This is the first part of a series about styling with React’s Material-UI, it aims to provide a thorough understanding of styling with makeStyles and will cover:

  • Styling regular DOM elements.
  • How makeStyles works.
  • Styling Material-UI’s components with makeStyles.
  • Dynamic styling by passing data to the makeStyles function.
  • Styling nested child elements – with the parent selector (&), and the reference operator ($).
Documentation Note: makeStyles is a function that links a style sheet with a function component using the hook pattern.

Throughout the article I will be explaining the above documentation note, in further detail.

*  *  *

This is a LENGTHY article, so please bear with me, if you are new to Material-UI, this is all I wished for when I’ve started to work with Material-UI.

I am currently focusing on Material-UI v4, and in later articles I will also discuss Material-UI v5.

Styling Regular DOM elements

One of the first things you need to do when working with Material-UI is to change its components default styles. Using makeStyles we can style Material-UI’s components and even regular DOM elements.

Please read the following slowly, as it contains all the information needed to get you started with styling with makeStyles.

*  *  *

We will start by styling a simple div element to look like a button, and build up on that example.

Copy
import { makeStyles } from "@material-ui/core";
const useStyles = makeStyles({
simpleButton: {
background: "deepskyblue",
color: "white",
fontWeight: "bold",
width: "150px",
padding: "10px",
},
});
export const Button = () => {
const classes = useStyles();
return (
<div className={classes.simpleButton} variant="contained">
I Read. You Learn.
</div>
);
};

This will result in a div styled as a button with a sky blue background, and a white bold text:

material-ui-div-styling

Let’s understand what just happened:

  • When calling makeStyles a new style sheet is associated with the styles I’ve used to style “simpleButton”.

  • A hook is returned from makeStyles, and used inside our Button functional component.

(useStyles is Material-UI’s naming convention for the hook being returned).

Copy
// makeStyles:
// 1. Associates a new style sheet with our styles.
// 2. returns a hook to our useStyles variable.
const useStyles = makeStyles({
simpleButton: {
background: "deepskyblue",
color: "white",
fontWeight: "bold",
width: "150px",
padding: "10px",
},
});
  • When calling the useStyles hook, I’ve received back an object containing the class names that will be added to the DOM.

  • Then I’ve used the class name generated for the property “simpleButton” and used it on the button as its className attribute.

Copy
export const Button = () => {
// Here I'm calling our hook,
// and getting an object with generated class names.
const classes = useStyles();
// Using the generated class name inside className
return (
<div className={classes.simpleButton} variant="contained">
I Read. You Learn.
</div>
);
};

The class name that will be returned from the useStyles hook:

Copy
{
simpleButton: "makeStyles-simpleButton-1";
}
The object returned from the hook contains our declared properties (rules) inside makeStyles as keys, and its values are the generated class names that will be injected into the DOM.

The generated class name “makeStyles-simpleButton-1” is made of:

  • Sheet name: “makeStyles”.

  • Rule name (our key inside makeStyles): “simpleButton”.

  • An identifier provided by Material-UI making sure our styles are encapsulated, and no styles will have the same class names. ( Avoiding any clashes with other styles )

When the page is loaded, Material-UI will inject our new styles at the bottom of the head tag:

material-ui-injected-dom-styles-head

Additional Notes about makeStyles:

  • Make styles can also receive a function.
  • That function must return an object that will hold our styles.
  • The function is automatically injected with the current theme being provided, if no theme is provided the default Material-UI theme will be injected.
  • The returned hook from makeStyles can be passed an object containing whatever properties you need to be available inside makeStyles.

The above notes will be explained in the next sections.

Now that you know how makeStyles work, let’s take it a step further.

Styling Material-UI's components

Material-UI exposes the classes we can override to style the components as we see fit.

When going over the API documentation of each component, you will find at the bottom a list of all available rules and their matching classes that you can override.

material-UI-docs-css

We will use the above rules “root” and “label” to style our button, and the matching classes (Global classes in the picture above), are the class names you will find injected to the DOM when using Material-UI’s components.

Now lets use the Button component provided by Material-UI and style it to look as before:

Copy
import { Button, makeStyles } from "@material-ui/core";
// Using the reserved rule names: "root", "label".
const useStyles = makeStyles({
root: {
background: "deepskyblue",
padding: "10px",
},
label: {
width: "150px",
color: "white",
fontWeight: "bold",
},
});
export const StyledButton = () => {
const classes = useStyles();
{
/*Passing the classes to the "classes" property.*/
}
return (
<Button classes={classes} variant="contained">
I Read. You Learn.
</Button>
);
};

This will result in a button styled as we did before:

material-simple-button

As you can see in the above code, we now used the rules exposed by Material-UI (“root”, “label”) to directly style it’s matching classes.

These are the generated classes:

Copy
{root: "makeStyles-root-15", label: "makeStyles-label-16"}

Because Material-UI adds an automatically generated identifier to the end of the class name, we cannot rely on it to style our components using regular SASS files, therefore this is a good option to style the component directly.

This time I’ve used the classes property, which allows us to override/extend the styles that are used on the component.

The classes property accepts an object that uses the exposed rules such as “root” and “label” to then apply their styles to the matching “.MuiButton-root” and”.MuiButton-label” classes that Material-UI uses.

You can still create other rules and style them like the previous “simpleButton” rule, and use them to style other elements.

*  *  *

Material-UI allows us to change the default class prefix being used – “makeStyles” and set our own prefix. The main usage would be for debugging your styles and easily finding them in the DOM.

You can change the name of the “makeStyles” prefix by adding an object as a second parameter to the makeStyles function:

Copy
const useStyles = makeStyles(
(theme) => {
return {
root: {
background: "skyblue",
padding: "10px",
},
};
},
{ name: "ireadyoulearn" }
);

Then it will inject the styles to the DOM with our prefix:

material-ui-unique-id

*  *  *

It is also possible to add a class name to the Button component and use CSS / SASS to style the button.

You might encounter issues with applying your styles with a more complicated Material-UI components, causing you to use !important to override it’s styles or you will need a more complex CSS selectors to reach your nested DOM elements.

Copy
<Button className="add-class" variant="contained">
I Read. You Learn.
</Button>
// Then access your "add-class" using an imported SCSS / CSS file.

The Order Of Precedence When Encountering Multiple MakeStyles Classes On The Same element

When a projects grow you will find yourself dealing with interesting situations that you might not understand at first.

Knowing the order of precedence when styling your component will come in very handy to understand why some styles take affect when you expect other wise.

Here I’ve created two different style sheets, and I’ve used them on the same DOM element className property.

Copy
const useStyles = makeStyles({
testOne: {
background: "red",
},
testTwo: {
background: "green",
},
});
const useStylesTwo = makeStyles({
testThree: {
background: "blue",
},
});
export const StyledButton = () => {
const classes = useStyles();
const classesTwo = useStylesTwo();
// Using all the created classes:
return (
<div
className={`
${classes.test}
${classesTwo.testThree}
${classes.testTwo}
`}
>
Test Order Of Precedence
</div>
);
};

The button color will be blue, determined by “useStylesTwo” with the rule of “testThree”, that is because the order of invocation for makeStyles MATTERS, the last one invoked will be the last to be injected into the bottom of the head tag and deciding the final style.

This is also important for Material-UI’s “withStyles”, and “styled” styling options, they allow you to style the component similarly to makeStyles.

Dynamic Styling And Accessing The theme

As indicated earlier, you can pass an object to the returned hook, making the data being passed accessible inside makeStyles.

It can be used to set the styles, or dynamically changing the styles based on the component state and props.

I’ve also indicated that when you pass a function it will be automatically injected with the available theme being used, and if no theme is used then the default Material-UI theme will be injected.

Let’s see this in action, the following will create a button that will change its color when clicking on it, it will do so based on a state variable being passed to the hook.

Copy
const useStyles = makeStyles((theme) => {
return {
root: {
background: (hookData) => hookData.backgroundColor,
padding: "10px",
},
label: {
width: "150px",
color: "white",
fontWeight: "bold",
},
};
});
export const ChangedStylesButton = () => {
const [backgroundColor, setBackgroundColor] = useState("deepskyblue");
const classes = useStyles({ backgroundColor });
const changeColor = () => {
setBackgroundColor("red");
};
return (
<div>
<Button onClick={changeColor} classes={classes} variant="contained">
I Read. You Learn.
</Button>
</div>
);
};

The above code will change the background color of the button when you click it to red.

I’ve made to following changes:

makeStyles accepts a function injected with a theme as its first argument, the theme can be used to set your components styles based on it, adding media queries and adding further logic.

I’ve also passed an object containing the background color to our returned hook.

Copy
const [backgroundColor, setBackgroundColor] = useState("deepskyblue");
// Passing the backgroundColor state to the hook:
const classes = useStyles({ backgroundColor });

To access the passed object you need to pass a function as an argument to the styles you are creating:

Copy
const useStyles = makeStyles((theme) => {
return {
root: {
// Passed a function to access the hook data
background: hookData => hookData.backgroundColor,
padding: "10px"
}
}
}
----------------------------------------------------------------------
const [backgroundColor,setBackgroundColor] = useState("deepskyblue")
// Passed the background color to makeStyles.
const classes = useStyles({ backgroundColor });

Styling Nested elements

Whenever you’ve added your own nested elements, you might need to style them from the makeStyles you’ve used to style the parent element.

I’ve tweaked our simple button example and added a nested "h4" element that I am going to style using the parent selector (“&”) inside makeStyles.

The parent selector allows us to be more specific about our styles, and generate less boilerplate styles ( repeating class names with an addition of the more specific selectors ).

Look at the following example:

Copy
const useStyles = makeStyles((theme) => {
return {
root: {
background: "deepskyblue",
padding: "10px",
// Using parent selector to reach the h4 element
"& h4": {
width: "150px",
color: "white",
fontWeight: "bold",
margin: 0,
},
},
};
});
export const ChangedStylesButton = () => {
const classes = useStyles();
return (
<div>
<Button classes={classes} variant="contained">
{/* Our nested element */}
<h4>I Read. You Learn.</h4>
</Button>
</div>
);
};

The above parent selector will allow us to access the h4 nested element. It will transform the styles to look like this in the DOM:

material-ui-nested-element-styling

Lets look at another example, styling the disabled state of the button.

here I will be setting the background color of the disabled state of the button to black, and when you hover the button it will change to a red background.

Copy
const useStyles = makeStyles((theme) => {
return {
root: {
background: "skyblue",
padding: "10px",
"&:disabled": {
// Activated pointer events to allow changing
// background color on hover.
pointerEvents: "auto",
background: "black",
},
"&:hover": {
"&:disabled": {
background: "red",
},
},
},
label: {
color: "white",
fontWeight: "bold",
},
};
});
export const ChangedStylesButton = () => {
const classes = useStyles();
return (
<div>
{/*Notice the disabled property*/}
<Button disabled classes={classes} variant="contained">
I Read. You Learn
</Button>
</div>
);
};

The styles will then compile to:

Copy
.makeStyles-root-181 {
padding: 10px;
background: skyblue;
}
.makeStyles-root-181:disabled {
background: black;
pointer-events: auto;
}
.makeStyles-root-181:hover:disabled {
background: red;
}
.makeStyles-label-182 {
color: white;
font-weight: bold;
}

Styling Nested Elements With The Reference operator

There is another way to achieve the same styling by using the local rule reference operator: “$”.

It took me a while to find information about this operator. you will see it being used in answers at stackoverflow, but with no explanation of what it actually does.

Well the reference “$” operator is added by a plugin called jss-plugin-nested, it comes by default using Material-UI.

This operator is used to reference a local rule in the same style sheet / inside the same object passed to makeStyles.

Docs: CSS In JS Docs

Let style the disabled state & hover again using the reference operator “$”:

Copy
const useStyles = makeStyles((theme) => {
return {
root: {
background: "skyblue",
padding: "10px",
},
disabled: {
"&$root": {
pointerEvents: "auto",
},
"&$root:hover": {
background: "red",
},
},
};
});

As you can see inside the disabled rule I am using the parent selector to add more specific selectors to our disabled rule.

I am referencing our “root” rule using “$root”, to make sure the pointer events is active ( it is set to “none” by default in the disabled state ), I do it to make sure that when we hover the button our mouse events will be active.

then I do the same for the hover state using “$root:hover” to style the background color when hovering a disabled button.

The above button style will compile to:

Copy
.makeStyles-root-177 {
padding: 10px;
background: skyblue;
}
.makeStyles-disabled-178.makeStyles-root-177 {
pointer-events: auto;
}
.makeStyles-disabled-178.makeStyles-root-177:hover {
background: red;
}

Lets look at another example to make sure that referencing a local rule has been understood.

Here we have a container rule, and a button rule, we can make sure that when the button is used inside the container and it is hovered, the text of the button will change to blue.

Copy
const styles = {
container: {
// Reference the local rule "button".
"&:hover $button": {
color: "blue",
},
},
button: {
color: "grey",
},
};

It will compile to:

Copy
.container-0:hover .button-1 {
color: blue;
}

By referencing a local rule using the reference “$” operator, you are actually taking the generated class and adding it after the class its being called in.

Using withStyles

This is more of a final note about using withStyles.

All that you’ve learned in this article is also available using the Higher-Order Component withStyles. Except for passing information to your hook component and accessing it with a function as an argument.

That is why I would not go in-depth about it here.

You should use makeStyles for all your functional components, and use withStyles primarily for class components.

An example could be found here, using codesandbox:

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

*  *  *

This was a long and lengthy article, I KNOW. But I am sure you are now able to style Material-UI components with ease.

© 2020-present Sagi Liba. All Rights Reserved