how to use css in JS for nested hover styles, Material UI
The correct syntax is "&:hover > div:hover": { ... }
.
Here is a working example demonstrating the syntax:
import React from "react";
import ReactDOM from "react-dom";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles({
navlink: {
border: "1px solid green",
fontSize: "16pt",
"&:hover": {
backgroundColor: "lightgreen"
},
"&:hover > div:hover": {
backgroundColor: "lightblue"
}
}
});
function App() {
const classes = useStyles();
return (
<div className="App">
<div className={classes.navlink}>
Hello <div>CodeSandbox</div>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
It also works to deeply nest with this syntax:
const useStyles = makeStyles({
navlink: {
border: "1px solid green",
fontSize: "16pt",
"&:hover": {
backgroundColor: "lightgreen",
"& > div:hover": {
backgroundColor: "lightblue"
}
}
}
});
Here is the relevant JSS documentation: https://cssinjs.org/jss-plugin-nested/?v=v10.0.0-alpha.24
Related answer:
- How do you change a style of a child when hovering over a parent using material-ui jss styles
How do you change a style of a child when hovering over a parent using MUI styles?
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>
);
}
How to display div element on hover another element using css-in-js?
Please check this example:
import React, {useState} from "react";
export default function ShowButtonHover() {
const [style, setStyle] = useState({display: 'none'});
return (
<div className="App">
<h2>Hidden message in the box. Move mouse in the box</h2>
<div style={{border: '1px solid gray', width: 300, height: 300, padding: 10, margin: 100}}
onMouseEnter={e => {
setStyle({display: 'block'});
}}
onMouseLeave={e => {
setStyle({display: 'none'})
}}
>
<div style={style}>This was hidden</div>
</div>
</div>
);
}
How to override style of nested Material UI component from the ancestors?
Let's say that the class name generated for myComponent
is myComponent-jss123
. The selector you used in your styles (&.MuiIconButton-root
) would be equivalent to .myComponent-jss123.MuiIconButton-root
which would match any element that has both of these classes applied to it. I believe your intent was to match icon buttons which are descendants of the div
on which you are applying the myComponent
class. In this case you need to use the descendant combinator represented by a space, so the appropriate styles would look like the following:
const styles = theme => ({
myComponent: {
"& .MuiIconButton-root": {
padding: 0
}
}
});
Here's a full working example:
import React from "react";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles({
myComponent: {
"& .MuiIconButton-root": {
padding: 0
}
}
});
const ThirdPartyComponent = () => {
return (
<div>
I'm a third party component that contains an IconButton:
<IconButton color="primary">
<DeleteIcon />
</IconButton>
</div>
);
};
export default function App() {
const classes = useStyles();
return (
<div className={classes.myComponent}>
<ThirdPartyComponent />
</div>
);
}
Related documentation:
- https://cssinjs.org/jss-plugin-nested/?v=v10.0.4#use--to-reference-selector-of-the-parent-rule
- https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors
- https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator
Material ui TreeItems change style conditionally based
After a little while this is what I got. I tried to match the design of the gif as good as possible.
I used this data
which I derived from the question. This example has one disabled node and parent node (my-photo.jpg
and zip
directory). The data array can be expanded endlessly if it matches TreeData
type.
export type TreeData = {
id: string;
name: string;
disabledButton: boolean;
children?: TreeData[];
};
const data: TreeData[] = [
{
id: "1",
name: "My Web Site",
disabledButton: false,
children: [
{
id: "2",
name: "images",
disabledButton: false,
children: [
{ id: "3", name: "logo.png", disabledButton: false },
{ id: "4", name: "body-back.png", disabledButton: false },
{ id: "5", name: "my-photo.jpg", disabledButton: true }
]
},
{
id: "6",
name: "resources",
disabledButton: false,
children: [
{
id: "7",
name: "pdf",
disabledButton: false,
children: [
{ id: "8", name: "brochure.pdf", disabledButton: false },
{ id: "9", name: "prices.pdf", disabledButton: false }
]
},
{
id: "10",
name: "zip",
disabledButton: true,
children: [{ id: "11", name: "test.zip", disabledButton: false }]
}
]
}
]
}
];
The CustomTreeView
consists of CustomTreeItem
that again makes use of CustomContent
.CustomContent
is used to handle all events and to display another icon (folder) next to the expandIcon
.
In the CustomTreeItem
I set the width to fit-content to not select the whole row, but to match the example from the gif:
const CustomTreeItem = (props: TreeItemProps) => (
<TreeItem
ContentComponent={CustomContent}
{...props}
sx={{
width: "fit-content",
".MuiTreeItem-content": {
py: "2px",
width: "fit-content"
}
}}
/>
);
The interesting part is the styling of the CustomTreeView
and its usage.
I have packed the classes into objects, which can be overwritten easily. The theming happens in the styles class which is in styles/Styles.ts
.
// Styles.ts
import { createTheme } from "@mui/material";
export const getMuiTheme = () =>
createTheme({
palette: {
primary: {
main: "#2160dd"
},
secondary: {
main: "#d3d3d3"
}
}
});
// CustomTreeView.tsx
const classes = {
focused: {
bgcolor: "transparent",
py: "1px",
px: "7px",
border: `1px solid ${getMuiTheme().palette.secondary.main}`
},
selected: {
bgcolor: getMuiTheme().palette.primary.main,
color: "white"
}
};
const CustomTreeView = ({ data }: { data: TreeData[] }) => {
return (
<Box mt={2} ml={2} bgcolor="white" width="300px">
<ThemeProvider theme={getMuiTheme()}>
<TreeView
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultEndIcon={<InsertDriveFile />}
sx={{
".MuiTreeItem-root": {
".Mui-focused:not(.Mui-selected)": classes.focused,
".Mui-selected, .Mui-focused.Mui-selected, .Mui-selected:hover":
classes.selected
}
}}
>
{renderTreeData(data)}
</TreeView>
</ThemeProvider>
</Box>
);
};
To render arbitrarily nested data we make use of the recursive method renderTreeData
. This way the data array
can be expanded endlessly as long as it matches TreeData
type.
export const renderTreeData = (data: TreeData[]) => {
return data.map((item) => (
<React.Fragment key={item.id}>
{item.children ? (
<CustomTreeItem
nodeId={item.id}
label={item.name}
disabled={item.disabledButton}
icon={<FolderOutlined />}
>
{renderTreeData(item.children)}
</CustomTreeItem>
) : (
<CustomTreeItem
nodeId={item.id}
label={item.name}
disabled={item.disabledButton}
icon={getFileIcon(item.name)}
/>
)}
</React.Fragment>
));
};
Live Demo
Related Topics
How to Send an Inner <Div> to the Bottom of Its Parent <Div>
Nested Rows with Bootstrap Grid System
Two Divs Next to Each Other, That Then Stack with Responsive Change
Difference Between Justify-Content VS Align-Items
Retina Displays, High-Res Background Images
Inline CSS Formatting Best Practices - Two Questions
Bootstrap 3 Changing Div Order on Small Screens Only
Override Rmarkdown Theme in Order to Change HTML Page Width
Webkit Text Aliasing Gets Weird During CSS3 Animations
Use Multiple CSS Filters at the Same Time
Scroll Part of Content in Fixed Position Container
Workaround for CSS Variables in Ie
Less/Sass Debugging in Chrome Dev Tools/Firebug
How to Standardize the Height of a Select Box Between Chrome and Firefox
Responsive Design with Media Query:Screen Size
Overflow Scroll CSS Is Not Working in the Div
Background:None VS Background:Transparent What Is the Difference