How to Hover on a Child Element Without Hovering on the Parent, Using Only CSS
Nitish Kumar Singh
Nov 16, 2023Hello, developers! In this blog post, we will learn "How to hover on a child element without hovering on the parent, using only CSS". I mean adding some style to the child element when hovering over it, following the conditions below:
- I want to use only CSS.
- I want to add only one CSS class to the top-level container, which contains many child and nested child elements.
- And add the same hovering effect to all those children when hovering over them, but not on the parent.
Why should I want to do this?
I faced this problem while developing a "Web Studio," where users can add HTML elements by drag & drop or clicking on buttons to create UI for web components or pages. Here, I want to show some visible indicators when an element gets hovered, selected on click, and focused on double-click. For example, showing a blue outline on hover, purple on select, and green on focus.
I know it's easy to do this with JavaScript, but I want to achieve it with CSS, at least for the hovering effect. So I conducted many Google searches and experiments to find a solution. I'm going to describe the solution using both JavaScript and CSS.
Let's assume a user creates a UI like the one below:
<div id='topLevelContainer'>
<h1>This is heading</h1>
<main>
<section>
<div>Here have some content</div>
</section>
<section>
<div>Here also have some different content</div>
</section>
</main>
</div>
Using JavaScript
In this method, we use the mouseover and mouseleave events on the top-level container. So we use the following code:
const container = document.getElementById("topLevelContainer");
var hoveredElement = null;
container.addEventListener("mouseover",(event)=>{
// remove first because when hovering on child after parent then mouseleave
// not fired on parent now so remove it here
if (hoveredElement) hoveredElement.classList.remove("hovered");
hoveredElement = event.target;
hoveredElement.classList.add("hovered");
});
container.addEventListener("mouseleave",(event)=>{
hoveredElement.classList.remove("hovered");
hoveredElement = null;
});
.hovered{
outline: 1px solid blue;
}
Because the mouseover and mouseleave events are fired rapidly on mouse movement, we use CSS.
Using CSS
In this method, we use the :hover
, :not
, :has
pseudo-classes, and the *
CSS selector. Use the following CSS for this:
#topLevelContainer:hover:not(:has(*:hover)),
#topLevelContainer *:hover:not(:has(*:hover)){
outline: 1px solid blue;
}
Explanation Points:-
- For adding the desired hover effect to the top-level container, we use
#topLevelContainer :hover:not(:has(*:hover))
. - For child and nested child elements, we use
#topLevelContainer *:hover:not(:has(*:hover))
. :hover:not(:has(*:hover))
: This CSS means hovering when there's no hover on any descendant elements.
As I mentioned at the beginning, elements can be selected or focused, and we want to add visual indicators according to the priority level of the state, which is focused > selected > hovered. Then we need to add :not(:focus-visible):not(.selected)
at the end of the selectors. So below is the final CSS:
#topLevelContainer:hover:not(:has(*:hover)):not(:focus-visible):not(.selected),
#topLevelContainer *:hover:not(:has(*:hover)):not(:focus-visible):not(.selected){
outline: 1px solid blue;
}
#topLevelContainer *:hover:not(:has(*:hover)):not(:focus-visible):not(.selected)
: This CSS means hovering when there's no hover on any child, it's not focused, and it doesn't have a .selected CSS class.
I hope you've understood the solution to this problem and learned something from this blog post.