How to Hover on a Child Element Without Hovering on the Parent, Using Only CSS

Nitish Kumar Singh

Nov 16, 2023
Photo by Greg Rakozy on Unsplash

Hello, 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.

Published on Nov 7, 2023
Comments (undefined)

Read More