Material-UI Theme Overrides: How To Override Children Styles Globally? - reactjs

I'm building an app with the Material-UI library for ReactJS. Using the Theme Overrides API, I'm trying to figure out how I can globally style a component, but only when it is a child of another specific component.
For example, I'm trying to set the background/text coloring of MenuItems inside a <Select> menu, where each <MenuItem> contains a <listItemText>. Here's my component:
import { MenuItem, Select, ListItemText } from '#material-ui/core';
import { MuiThemeProvider } from '#material-ui/core/styles';
import * as React from 'react';
import theme from './theme';
const MySelect = props => {
return (
<MuiThemeProvider theme={theme}>
<Select variant="standard" value="2" open>
<MenuItem value="1">
<ListItemText>One</ListItemText>
</MenuItem>
<MenuItem value="2">
<ListItemText>Two</ListItemText>
</MenuItem>
<MenuItem value="3">
<ListItemText>Three</ListItemText>
</MenuItem>
<MenuItem value="4">
<ListItemText>Four</ListItemText>
</MenuItem>
</Select>
</MuiThemeProvider>
);
};
export default MySelect;
Unfortunately, applying a color to the <MenuItem> directly doesn't work because the <ListItemText> overrides it with a <Typography> that has its own coloring set. This is fine for a non-hovered, non-selected state, but if I style the "selected" MenuItem to have a darker background, I need it to have a lighter text.
Here is my theme file:
import { createMuiTheme, createStyles } from '#material-ui/core';
const myTheme = createMuiTheme({
overrides: {
MuiMenuItem: createStyles({
root: {
'&&:hover': {
backgroundColor: 'pink',
color: 'white'
}
},
selected: {
'&&': {
backgroundColor: 'blue',
color: 'white'
},
'&&:hover': {
backgroundColor: 'darkblue',
color: 'white'
}
}
}),
// How do I enforce this ONLY inside of MuiMenuItem and only for
// the selected variant of that?
MuiTypography: {
subheading: {
color: 'white'
}
}
}
});
export default myTheme;
So, my question is: is there a way to do this using just Theme Overrides? Or do I need to conditionally pass this styling into the <ListItemText> component using props? Since most of the styling here fits nicely into Theme Overrides, that seems like a nicer way to do it, but maybe I'm misusing the API.
For a working demo of my above code, see: https://codesandbox.io/s/3r9mkxq231
Any insight is welcome! Thank you!

One way to accomplish that is to target the descendant html element (e.g. the span for the ListItemText) from the ancestor styles (MenuItem in this case).
Here's an example of how the MenuItem.selected style could be specified:
selected: {
"&&": {
backgroundColor: "blue",
color: "white",
"&& span": {
color: "white"
}
},
"&&:hover": {
backgroundColor: "darkblue",
color: "white"
}
}
The full code (forked from your CodeSandbox) is here:

First of all, I don't think we can do that in the theme overrides. Theme overrides is a way to override a default style configuration of an existing material-ui component.
Second, I don't think you need to make it too complex with conditional statements. This can be solved without that also. I didn't understand why did you need to use <ListItemText> when <MenuItem> itself has functionality to display text.
Just simply remove <ListItemText> from you code and then you can use theme overrides to modify your <MenuItem> as you like.
Find the modified code here : https://codesandbox.io/s/30p3o4jjz5
Let me know if this clarifies your doubt.

Yes you can do this in theme overrides using jss-nested syntax:
const myTheme = createMuiTheme({
overrides: {
MuiMenuItem: createStyles({
root: {
"&&:hover": {
backgroundColor: "pink",
"& *": {
color: "white"
}
}
},
selected: {
"&&": {
backgroundColor: "blue",
"& *": {
color: "white"
}
},
"&&:hover": {
backgroundColor: "darkblue",
"& *": {
color: "white"
}
}
}
})
}
});
export default myTheme;
See working codepen here: https://codesandbox.io/embed/308mk7k5x6?fontsize=14

Related

Issue trying to override mui theme in Reactjs

I have a navbar with some list items and I want to override the style of MuiListItemText when MuiListItemButton is selected. I can override the hightlight but can't do it with the text or the icon. I can override the ListItemText but all of them, I just want to override if 'selected' (Mui-selected).
I'm using Mui 5.2.0.
This is my approach.
const Theme = createTheme({
components: {
MuiListItemButton: {
styleOverrides: {
root: {
borderRadius: '4px',
padding: '12px',
'&.Mui-selected': {
backgroundColor: '#F2F2EA',
MuiListItemText: {
styleOverrides: {
secondary: {
color: 'red',
...
You should define the overriding theme structure in the same depth.
const Theme = createTheme({
components: {
MuiListItemButton: {
styleOverrides: {
root: {...}
}
},
// it should be another component, not a nested structure
MuiListItemText: {
styleOverrides: {
secondary: {
...
And you can customize an individual component by using sx prop, styled-components, or makeStyles API.
https://mui.com/customization/how-to-customize/
Here's an example using styled-components.
const CustomListItemButton = styled(ListItemButton)(() => ({
borderRadius: '4px',
padding: '12px',
'&.Mui-selected': {
backgroundColor: '#F2F2EA',
...
}));
Try this:
import { createMuiTheme, ThemeProvider } from '#material-ui/core/styles';
I don't think it's the best approach but I was able to do it with something like this:
<ListItemText
secondary={
<Typography
variant="body2"
style={selected === '/' ? { color: 'red' } : { color 'green' }>
Item Text</Typography> } />

How to keep the background color of disabled Button in Material-UI?

I want to change the global style of the disabled Button component from Material-UI. But the problem is I am unable to keep the original color scheme of the button.
Consider this button:
<Button color="secondary" disabled={isLoading}>Create Account</Button>
Now by default Mui-disabled would be attached to this button. Whose color and the background-color are taken from theme.palatte.action property. So this disabled button would be having CSS as:
color: rgba(0,0,0,0.26);
box-shadow: none;
background-color: rgba(0,0,0,0.12);
But I want my disabled button to maintain its original color ("primary, secondary", "error" etc.) added with an opacity of 0.7. By default, the cursor events are set to none by MUI.
I tried it using the custom theme but I don't know how to maintain the original color of the button. E.g if the Button is primary keep the primary color, if the Button is secondary keep the secondary colors.
MuiButton: {
styleOverrides: {
root: {
textTransform: "none",
boxShadow: "none",
"&.Mui-disabled": {
// background: "initial",
// color: "initial",
opacity: .7
}
},
}
}
Of course, I don't want to write a custom code for each code. I can do this even by creating a wrapper around the MUI's button and use that wrapper everywhere in my code.
But I want to do it the MUI way by overriding the theme.
How can I implement a global solution?
Is this what you want? just don't set the disabledBackground color in the palette:
import Button, { buttonClasses } from "#mui/material/Button";
import { createTheme, ThemeProvider } from "#mui/material/styles";
const defaultTheme = createTheme();
const theme = createTheme({
palette: {
action: {
disabledBackground: "", // don't set the disable background color
disabled: "white", // set the disable foreground color
}
},
components: {
MuiButtonBase: {
styleOverrides: {
root: {
[`&.${buttonClasses.disabled}`]: {
opacity: 0.5
},
// Fix ButtonGroup disabled styles.
[`&.${toggleButtonClasses.root}.${buttonClasses.disabled}`]: {
color: defaultTheme.palette.action.disabled,
borderColor: defaultTheme.palette.action.disabledBackground
}
}
}
}
}
);
At the current time I'm writing. There is also other components that reuses the disableBackground property, make sure you take a look and are okay with that too. I display them all in the example below.
Live Demo
With the way that the disabled state is handled in the default styles, I think you need to redefine the default colors within your override in order for it to work.
In the source code you can find how the default colors are defined for the text, outlined, and contained variants.
The example below demonstrates overriding the disabled styles for all three variants with opacity: 0.7 by redefining the non-disabled colors within the "&.Mui-disabled" style overrides.
import * as React from "react";
import Stack from "#mui/material/Stack";
import Button from "#mui/material/Button";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import { alpha } from "#mui/system";
const defaultTheme = createTheme();
const colors = [
"inherit",
"primary",
"secondary",
"success",
"error",
"info",
"warning"
];
const containedColorStyles = {};
const textColorStyles = {};
const outlinedColorStyles = {};
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
colors.forEach((color) => {
containedColorStyles[`&.MuiButton-contained${capitalizeFirstLetter(color)}`] =
color === "inherit"
? {
backgroundColor: defaultTheme.palette.grey[300],
color: defaultTheme.palette.getContrastText(
defaultTheme.palette.grey[300]
)
}
: {
backgroundColor: defaultTheme.palette[color].main,
color: defaultTheme.palette[color].contrastText
};
textColorStyles[`&.MuiButton-text${capitalizeFirstLetter(color)}`] =
color === "inherit"
? {
color: "inherit"
}
: {
color: defaultTheme.palette[color].main
};
outlinedColorStyles[`&.MuiButton-outlined${capitalizeFirstLetter(color)}`] =
color === "inherit"
? {
color: "inherit",
borderColor:
defaultTheme.palette.mode === "light"
? "rgba(0, 0, 0, 0.23)"
: "rgba(255, 255, 255, 0.23)"
}
: {
color: defaultTheme.palette[color].main,
borderColor: `${alpha(defaultTheme.palette[color].main, 0.5)}`
};
});
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
textTransform: "none",
boxShadow: "none",
"&.Mui-disabled": {
...containedColorStyles,
...textColorStyles,
...outlinedColorStyles,
opacity: 0.7
}
}
}
}
}
});
export default function ColorButtons() {
const variants = ["text", "outlined", "contained"];
return (
<ThemeProvider theme={theme}>
{variants.map((variant) => (
<div key={variant}>
{capitalizeFirstLetter(variant)} Variant
<Stack direction="row" spacing={2}>
{colors.map((color) => (
<Button key={color} variant={variant} color={color}>
{capitalizeFirstLetter(color)}
</Button>
))}
</Stack>
Disabled {capitalizeFirstLetter(variant)} Variant
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
{colors.map((color) => (
<Button key={color} disabled variant={variant} color={color}>
{capitalizeFirstLetter(color)}
</Button>
))}
</Stack>
</div>
))}
</ThemeProvider>
);
}
I have a nice solution for now.
I use the ownerState props to get the color props. Next, I use it in the theme's palette. The following example illustrates how to do this for a contained button.
let theme = createTheme({
palette: {
// Your custom palette
},
});
theme = createTheme(theme, {
components: {
MuiButton: {
defaultProps: {
disableElevation: true
},
styleOverrides: {
root: ({ ownerState }) => ({
'&.MuiButton-contained.Mui-disabled': {
backgroundColor: theme.palette[ownerState.color].main,
}
}),
}
}
}
});

How to add css variable to createMuiTheme

I'm using MuiThemeProvider in my component and I wanted to style the MenuItem from MUI. What I did here is creating a variable with createMuiTheme like this
const themes = createMuiTheme({
overrides: {
MuiListItem: {
root: {
"&$selected": {
background: '#459FB6',
color: '#fff',
}
}
}
}
});
and I passed this to my ThemeProvider like this
<MuiThemeProvider theme={themes}>
<MenuItem>
Menu Item
</MenuItem>
</MuiThemeProvider>
I also have an object with a color palette and I want to use them inside my themes. My question is how to achieve something like this
const themes = createMuiTheme({
overrides: {
MuiListItem: {
root: {
"&$selected": {
background: colors.blue, // here i want to use my custom variable
color: colors.white, // here i want to use my custom variable
}
}
}
}
});
Thanks in advance!
If someone else stuck with the same problem, I created this answer. I achieved this by using makeStyles instead of createMuiTheme and ThemeProvider. The code sample is down below
const useStyle = makeStyles({
root: {
"&:hover": {
backgroundColor: `${theme.colors.highlight}`,
color: `${theme.colors.white}`,
}
},
selected: {
"&$selected": {
backgroundColor: `${theme.colors.textLink}`,
color: `${theme.colors.white}`,
},
"&$selected:hover": {
backgroundColor: `${theme.colors.highlight}`,
color: `${theme.colors.white}`,
},
},
});
const classes = useStyle();
and then I passed classes to my MenuItem component like this
<MenuItem classes={{ root: classes.root, selected: classes.selected}}>
Menu Item
</MenuItem>
and voila!

How to override Material-UI MenuItem selected background color?

Currently I am struggling with setting the background color of a MenuItem component which is selected to a different color. (without having to using !important to force it)
The component code:
<MenuItem
classes={{
root: this.props.classes.root,
selected: this.props.classes.selected
}}
value={10}>
Alfabetical
</MenuItem>
This is the css:
const homePageStyle = (theme) => ({
root: {
width: "300px"
},
selected: {
backgroundColor: "turquoise !important",
color: "white",
fontWeight: 600
}
});
What do I want to achieve?
I would like to set the backgroundColor of the MenuItem without having to set the !important flag. I've done that with plenty of components but this seems not work around at the moment.
I am using version "#material-ui/core": "^1.0.0-rc.0",
I just made a working example here
For your selected class to be taken into account, you have to set the selected property of your MenuItem component to true
<MenuItem
onClick={this.handleClose}
selected
classes={{ selected: classes.selected }}
>
I'm doing it this way to change the MenuItem background of selected. (selected prop provided by material UI).
export default createMuiTheme({
overrides: {
MuiMenuItem: { // For ListItem, change this to MuiListItem
root: {
"&$selected": { // this is to refer to the prop provided by M-UI
backgroundColor: "black", // updated backgroundColor
},
},
},
},
});
These are the defaults that can be overridden https://material-ui.com/customization/default-theme/#default-theme
Reference: https://material-ui.com/customization/components/#global-theme-override
Note: I'm using Material-UI version 4.9.11
In MUI v5, this is how you do it:
<Select
MenuProps={{
sx: {
"&& .Mui-selected": {
backgroundColor: "pink"
}
}
}}
>
Live Demo
You can updated your styles to this:
const homePageStyle = (theme) => ({
root: {
width: "300px"
},
selected: {
'&.Mui-selected': {
backgroundColor: "turquoise",
color: "white",
fontWeight: 600
}
}
});
This is because of how material-ui styles this component: .MuiListItem-root.Mui-selected
The specificity of those two classes is taking priority over the provided override.
To customize component style in Mui v5, you can try this:
MuiListItemButton: {
styleOverrides: {
root: {
'&.Mui-selected': {
backgroundColor: '#e44444',
}
}
},
},
You'll want to use MuiListItemButton component, because "selected" is deprecated in MuiListItem.
Customizing components: https://mui.com/material-ui/customization/theme-components/

Set the background color of a Snackbar in MUI

I am using a Snackbar component from MUI. At the moment the Snackbar appears in black. Do you know how I can change the color? Setting background-color only changes the color of the whole div in which the Snackbar is presented. It does not change the color of the Snackbar.
With material-ui 1.0 (or higher) you should override the root CSS class from the SnackbarContent component with the prop ContentProps.
Here is an example:
const styles = {
root: {
background: 'red'
}
};
class MySnackbar extends Component {
render() {
const { classes } = this.props;
return (
<Snackbar
ContentProps={{
classes: {
root: classes.root
}
}}
message={<span>Some message</span>}
/>
);
}
}
export default withStyles(styles)(MySnackbar);
If someone do not want to pass style as props, we can also write a class in a css file and assign it to the ContentProps, like this:
ContentProps={{
classes: {
root: 'errorClass'
}
}}
and in index.css file:
.errorClass { color: 'red' }
With the current material-ui version (4.3.0) there is a even simpler approach than the ContentProps way. Instead of the message attribute you can use a SnackbarContent as child and simply call style={} on it
<Snackbar
open={this.state.showSnackbar}
autoHideDuration={3000}
onClose={() => {this.setState({showSnackbar: false})}}
>
<SnackbarContent style={{
backgroundColor:'teal',
}}
message={<span id="client-snackbar">Hello World</span>}
/>
</Snackbar>
You have to set the bodyStyle property:
<Snackbar bodyStyle={{ backgroundColor: 'teal', color: 'coral' }} />
You can find more info about how you customize the snackbars in the documentation
The root component of the Snackbar only concerns about positioning itself correctly, if you want to change the appearance of the physical Snackbar, you need to target the SnackbarContent via ContentProps prop. In v5, you can use the sx to do that easily:
<Snackbar
ContentProps={{
sx: {
background: "red"
}
}}
Another way is to create a custom variant for your Snackbar:
import { createTheme, ThemeProvider } from "#mui/material/styles";
const theme = createTheme({
components: {
MuiSnackbar: {
variants: [
{
props: { variant: "trouble" },
style: {
"& .MuiSnackbarContent-root": {
background: "orange"
}
}
},
{
props: { variant: "bigTrouble" },
style: {
"& .MuiSnackbarContent-root": {
background: "red"
}
}
}
]
}
}
});
<Snackbar variant="bigTrouble"
To use with typescript, you also need to update the prop type of Snackbar:
declare module "#mui/material/Snackbar" {
interface SnackbarProps {
variant: "trouble" | "bigTrouble";
}
}
With MUI v5 the optimal option to customize Snackbar (background, text color or any other styles) is to use sx prop and specific classNames for variants:
<Snackbar
sx={{
'& .SnackbarItem-variantSuccess': {
backgroundColor: colors.primary, //your custom color here
},
'& .SnackbarItem-variantError': {
backgroundColor: colors.alert, //your custom color here
},
'& .SnackbarItem-variantWarning': {
backgroundColor: colors.attention, //your custom color here
},
'& .SnackbarItem-variantInfo': {
backgroundColor: colors.secondary, //your custom color here
},
}}
autoHideDuration={10000}
//...other props here>
</Snackbar>
The same approach can be applied to notistack library to customize their snackbar.

Resources