Hiding element visible with :focus-within when sibling has :hover state

I’m working on a navigation component and want the nav element to be visible, using display: block when it has :focus-within for keyboard navigation. This state seems to be applying when I click the nav button, but remains visible when I hover sibling elements. I would like the focus-within state to be hidden when a sibling has the hover state.

Given the following HTML

    <ul>
      <li>
        <button>
        One
        </button>
        <span>
          submenu 1
        </span>
      </li>
      <li>
        <button>
          Two
        </button>
        <span>
          submenu 2
        </span>
      </li>
      <li>
        <button>
          Three
        </button>
        <span>
          submenu 3
        </span>
          
      </li>
    </ul>

and the following CSS

span {
  display: none;
}

li {
  &:hover > span {
    display: block;
  }
  
  &:focus-within > span {
    display: block;
  }
}

Codepen implementation

I can show the submenu items when I click or keyboard navigate the page. However, if I click <button>One and then hover <button>Two, submenu 1 remains visible. The submenu should only be visible if it has 1) :hover state or 2) :focus-within state (but no sibling has hover state).

I would like no more than 1 submenu visible at once. Is this possible to achieve with CSS?

>Solution :

Yes, it is:

li:hover ~ li:focus-within,
li:focus-within:has(~ li:hover) {
  display: none;
}

Note that :has() is not very well supported (yet).

Try it:

span {
  display: none;
}

li:hover > span {
  display: block;
}

li:focus-within > span {
  display: block;
}

li:hover ~ li:focus-within,
li:focus-within:has(~ li:hover) {
  display: none;
}
<ul>
  <li>
    <button>One</button>
    <span>submenu 1</span>
  </li>
  <li>
    <button>Two</button>
    <span>submenu 2</span>
  </li>
  <li>
    <button>Three</button>
    <span>submenu 3</span>
  </li>
</ul>

Leave a Reply