Search code examples
cssinheritancehover

Can CSS detect the difference between an actual hover and an inherited hover?


If the mouse hovers over an element in an HTML page, the browser creates a :hover pseudo-class for that element and for all its parents, even if the mouse is not hovering over any part of the parent element itself.

You can see this in the snippet below. In the "Two Nested Divs" example, hovering over the blue square will also create a :hover pseudo-class for its parent red square.

Is there a CSS selector that can be used to react to the mouse only if the mouse is over the red square itself?

I have shown a workaround in the Three Overlapping Divs example. Here, the red and blue squares are siblings, and there is an invisible square div at the intersection of the visible squares. This invisible square intercepts the pointer-event that triggers the creation of a :hover pseudo-class. This allows me to write CSS which reacts as if there were a :hover pseudo-class for both visible squares. It also prevents a :hover class from being attached to the red square when the mouse is over the blue square, because there is no inheritance.

However, in the Three Overlapping Divs example, the blue square is no longer a child of the red square. In my real-life case, this inheritance is something that I want.

:root {
  --line: 1px;
  --side: 1vmin;
  --size: max(min(100vh, 50vw), min(50vh, 100vw));
  --font: calc(var(--size) / 20);
  --rect: calc(var(--size) * 0.666 - var(--line));
  --drop: calc(var(--rect) * 0.5);
  --edge: calc(var(--drop) - var(--side));
  --red:  #9009;
  --blue: #0099;
  --bg:   #040;
}

body {
  margin: 0;
  height: 100vh;
  color: #ddd;
  background-color: #222;
  font-size: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

@media (aspect-ratio < 1/1) {
  main {
    width: var(--size);
  }
}

div {
  position: relative;
  box-sizing: border-box;
}

div[id] {
  font-size: var(--font);
  display: inline-block;
  width: var(--size);
  height: var(--size);
  border: var(--line) solid #ddd;

  p {
    position: absolute;
    pointer-events: none;
    z-index: 1;

    span {
      display: block;
      width: var(--rect);
      margin-top: 0.5em;
      font-size: 0.7em;
    }
  }

  div.red,
  div.blue,
  div.overlap {
    width: var(--rect);
    height: var(--rect);
    border: var(--side) solid red;
  }
  div.red:hover {
    background-color: var(--red);
  }

  div.blue {
    border-color: blue;

    &:hover {
      background-color: var(--blue);
    }
  }

  &#two {
    background-color: var(--bg);
    
    .blue {
      top: var(--edge);
      left: var(--edge);
    }
  }

  &#three {
   .blue {
      position: absolute;
      top: var(--drop);
      left: var(--drop);
    }

      .overlap {
        position: absolute;
        top: var(--drop);
        left: var(--drop);
        width: var(--drop);
        height: var(--drop);
        border-color: transparent;
      }
  }
}

#three:has(.overlap:hover) {
  .red {
    background-color: var(--red);
  }
  .blue {
    background-color: var(--blue);
  }
}
<div id="two">
  <p>Two nested divs
    <span>Roll over the red square to highlight it</span>
    <span>Rolling over the blue square also highlights its parent red square, even when the mouse is not over the intersection</span>
  </p>
  <div class="red">
    <div class="blue"></div>
  </div>
</div>
<div id="three">
  <p>Three overlapping divs<span>Roll over either square to highlight it</span><span>Roll over the intersection to highlight both squares</span></p>
  <div class="red"></div>
  <div class="blue"></div>
  <div class="overlap"></div>
</div>


Solution

  • Make .overlap child of .blue and .blue child of .red, then change the condition of .red a bit.

    :root {
      --line: 1px;
      --side: 1vmin;
      --size: max(min(100vh, 50vw), min(50vh, 100vw));
      --font: calc(var(--size) / 20);
      --rect: calc(var(--size) * 0.666 - var(--line));
      --drop: calc(var(--rect) * 0.5);
      --edge: calc(var(--drop) - var(--side));
      --red: #9009;
      --blue: #0099;
      --bg: #040;
    }
    
    body {
      margin: 0;
      height: 100vh;
      color: #ddd;
      background-color: #222;
      font-size: 0;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    @media (aspect-ratio < 1/1) {
      main {
        width: var(--size);
      }
    }
    
    div {
      position: relative;
      box-sizing: border-box;
    }
    
    div[id] {
      font-size: var(--font);
      display: inline-block;
      width: var(--size);
      height: var(--size);
      border: var(--line) solid #ddd;
      p {
        position: absolute;
        pointer-events: none;
        z-index: 1;
        span {
          display: block;
          width: var(--rect);
          margin-top: 0.5em;
          font-size: 0.7em;
        }
      }
      div.red,
      div.blue,
      div.overlap {
        width: var(--rect);
        height: var(--rect);
        border: var(--side) solid red;
      }
      div.red:hover:not(:has(.blue:hover)),
      div.red:has(.overlap:hover) {
        background-color: var(--red);
      }
      div.blue {
        border-color: blue;
        &:hover {
          background-color: var(--blue);
        }
      }
      &#three {
        .blue {
          position: absolute;
          top: var(--drop);
          left: var(--drop);
        }
        .overlap {
          position: absolute;
          width: var(--drop);
          height: var(--drop);
          border-color: transparent;
        }
      }
    }
    <div id="three">
      <p>Three overlapping divs<span>Roll over either square to highlight it</span><span>Roll over the intersection to highlight both squares</span></p>
      <div class="red">
        <div class="blue">
          <div class="overlap"></div>
        </div>
      </div>
    </div>