ZenMaster
ZenMaster

Reputation: 12746

Change Material-UI ListItem children on hover/active

Consider the following component structure of a side-bar navigation:

<ListItem button dense component={CustomNavLink} to="/dashboard">
    <ListItemIcon>
        <DashboardIcon />
    </ListItemIcon>
    <ListItemText primary="Dashboard" />
</ListItem>

The task is to change the ListItemIcon and ListItemText appearance on hover or when the CustomNavLink becomes active.

Note that CustomNavLink is an extended React Router's NavLink component that gets an active class applied to when it matches with the current route.

The following, somewhat hacky way achieves that (abridged and simplified for clarity) via classes property:

const styles =  {
    root: {
        ...
        '&.active, &:hover, &.active:hover': {
            '& path': {
                fill: 'red'
            },
            '& span': {
                color: 'red'
            }
        }
    }
};

(classes are then applied to the ListItem component)

This seems like an extremely lousy way of going about it, as the structure of the nested components leaks into the parent's styling... which is akin to doing this in the "old" CSS:

div:hover > ul > li > a {
    color: red;
}

What is the idiomatic Material-UI way of solving this?

For reference, this is how it would be done in styled-components:

const CustomNavLink = styled(NavLink)`
    ...
    &:hover {
        ${ListItemIcon} {
            path: {
                fill: red;
            }
        }

        ${ListItemText} {
            color: red;
        }
    }
`;

Upvotes: 24

Views: 20022

Answers (3)

NearHuscarl
NearHuscarl

Reputation: 81643

This is one way to do it in MUI v5, first import the following components:

import ListItem, { listItemClasses } from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";

Then define the your custom NavLink, this NavLink will add the active className when it matches the current route:

function MyNavLink(props) {
  return <NavLink {...props} activeClassName="active" />;
}

Then in the List component, add the following styles to style the ListItem in active and hover state:

<List
  sx={{
    [`& .active, & .${listItemClasses.root}:hover`]: {
      color: "red",
      fontWeight: "bold",
      "& svg": {
        fill: "red"
      }
    }
  }}
>
  <ListItem component={MyNavLink} to="/" exact>
    <ListItemButton>
      <ListItemIcon>
        <InboxIcon />
      </ListItemIcon>
      <ListItemText primary="Home" />
    </ListItemButton>
  </ListItem>
  {/* other NavLink component */}
</List>

Live Demo

Codesandbox Demo

Upvotes: 2

Akash Kumar Seth
Akash Kumar Seth

Reputation: 1701

Expanding answer by @Patel Charul. In case you want to change the style of multiple children on hover.

const wrapperStyles = {
  parent: {
    backgroundColor: 'yellow',
    '&:hover': {
      '& $child1': {
        color: 'red'
      },
      '& $child2': {
        color: 'blue'
      }
  },
  child1: {
    fontSize: '2em',
    padding: 24
  },
  child2: {
    fontSize: '4em',
    padding: 28
  }
}

Upvotes: 12

Charul Patel
Charul Patel

Reputation: 195

Please Try following example for Change Material UI ListItem children on hover/active

const wrapperStyles = {
  parent: {
    backgroundColor: 'yellow',
    '&:hover $child': {
      color: 'red'
    }

  },
  child: {
    fontSize: '2em',
    padding: 24
  }
}

Upvotes: 17

Related Questions