How do you change a style of a child when hovering over a parent using MUI styles?
I'm using MUI in react. Let's say I have this component with these styles:
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100]
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
I want to change the style of addIcon
when hovering over outerDiv
using the styles above.
Here's my example.
Below is an example of the correct syntax for v4 ("& $addIcon"
nested within &:hover
). Further down are some v5 examples.
import * as React from "react";
import { render } from "react-dom";
import { Grid, makeStyles } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
const useStyles = makeStyles(theme => ({
outerDiv: {
backgroundColor: theme.palette.grey[200],
padding: theme.spacing(4),
'&:hover': {
cursor: 'pointer',
backgroundColor: theme.palette.grey[100],
"& $addIcon": {
color: "purple"
}
}
},
addIcon: (props: { dragActive: boolean }) => ({
height: 50,
width: 50,
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})
}));
function App() {
const classes = useStyles();
return (
<Grid container>
<Grid item className={classes.outerDiv}>
<AddIcon className={classes.addIcon} />
</Grid>
</Grid>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Related documentation and answers:
- https://cssinjs.org/jss-plugin-nested?v=v10.0.0#use-rulename-to-reference-a-local-rule-within-the-same-style-sheet
- how to use css in JS for nested hover styles, Material UI
- Material UI: affect children based on class
- Advanced styling in material-ui
For those who have started using Material-UI v5, the example below implements the same styles but leveraging the new sx
prop.
import Grid from "@mui/material/Grid";
import { useTheme } from "@mui/material/styles";
import AddIcon from "@mui/icons-material/Add";
export default function App() {
const theme = useTheme();
return (
<Grid container>
<Grid
item
sx={{
p: 4,
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
}}
>
<AddIcon
className="addIcon"
sx={{
height: "50px",
width: "50px",
color: theme.palette.grey[400],
mb: 2
}}
/>
</Grid>
</Grid>
);
}
Here's another v5 example, but using Emotion's styled
function rather than Material-UI's sx
prop:
import Grid from "@mui/material/Grid";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import AddIcon from "@mui/icons-material/Add";
import styled from "@emotion/styled/macro";
const StyledAddIcon = styled(AddIcon)(({ theme }) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
}));
const StyledGrid = styled(Grid)(({ theme }) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
[`${StyledAddIcon}`]: {
color: "purple"
}
}
}));
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<StyledGrid item>
<StyledAddIcon />
</StyledGrid>
</Grid>
</ThemeProvider>
);
}
And one more v5 example using Emotion's css prop:
/** @jsxImportSource @emotion/react */
import Grid from "@mui/material/Grid";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import AddIcon from "@mui/icons-material/Add";
const theme = createTheme();
export default function App() {
return (
<ThemeProvider theme={theme}>
<Grid container>
<Grid
item
css={(theme) => ({
padding: theme.spacing(4),
backgroundColor: theme.palette.grey[200],
"&:hover": {
backgroundColor: theme.palette.grey[100],
cursor: "pointer",
"& .addIcon": {
color: "purple"
}
}
})}
>
<AddIcon
className="addIcon"
css={(theme) => ({
height: "50px",
width: "50px",
color: theme.palette.grey[400],
marginBottom: theme.spacing(2)
})}
/>
</Grid>
</Grid>
</ThemeProvider>
);
}
Possibly an obvious point, but just to add to the answer above: if you are referencing a separate className, don't forget that you also need to create it in the makeStyles hook or else it won't work. For instance:
const useStyles = makeStyles({
parent: {
color: "red",
"&:hover": {
"& $child": {
color: "blue" // will only apply if the class below is declared (can be declared empty)
}
}
},
// child: {} // THIS must be created / uncommented in order for the code above to work; assigning the className to the component alone won't work.
})
const Example = () => {
const classes = useStyles()
return (
<Box className={classes.parent}>
<Box className={classes.child}>
I am red unless you create the child class in the hook
</Box>
</Box>
)
}
This denotes the current selector which is the parent component:
'&': { /* styles */ }
This means the parent component in hover state:
'&:hover': { /* styles */ }
This means the child component inside the parent that is in hover state:
'&:hover .child': { /* styles */ }
You can also omit the ampersand &
if you're using a pseudo-class:
':hover .child': { /* styles */ }
Complete code using sx
prop (The same style object can also be used in styled()
):
<Box
sx={{
width: 300,
height: 300,
backgroundColor: "darkblue",
":hover .child": {
backgroundColor: "orange"
}
}}
>
<Box className="child" sx={{ width: 200, height: 200 }} />
</Box>