DEV Community

Alex
Alex

Posted on

Mastering :focus-within

Dropdown-on-hover pattern is used in interfaces for a very long time. And most of that time, these dropdowns are not fully acccesible. For example, if you navigate via keyboard there is no chance that you will get into this dropdown and select something hidden in it.

But everything has changed when :focus-within landed in browsers. And according to caniuse.com this code is enough for about 81% of users to make them a little happier:

<div class="dropdown" tabindex="0">
  <p class="dropdown__title">This is dropdown</p>
  <div class="dropdown__wrapper">
    <a href="#">Some hidden link</a>
  </div>
</div>
.dropdown {
  position: relative;
}
.dropdown__wrapper {
  width: 0;
  height: 0;
  overflow: hidden;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 1;
}
.dropdown:hover .dropdown__wrapper,
.dropdown:focus-within .dropdown__wrapper {
  width: 100%;
  height: auto;
  overflow: visible;
}

Note that you have to add tabindex="0" to the dropdown container, so it become focusable.

So, we’re done. But what about that near 19% of browsers that doesn’t support :focus-within? Javascript all the things!

// so here is module pattern begins
;(function () {
  // get all of dropdowns on page and define active class
  const dropdowns = Array.from(document.querySelectorAll('.dropdown'))
  const dropdownActiveClass = 'dropdown--active'

  // add event listeners to focusin and focusout to our dropdowns
  dropdowns.forEach(dropdown => {
    dropdown.addEventListener('focusin', focusinListener)
    dropdown.addEventListener('focusout', focusoutListener)
  })

  // if focus is inside dropdown, add active class
  function focusinListener (event) {
    event.target.closest('.dropdown').classList.add(dropdownActiveClass)
  }

  // if focused element is not dropdown, remove active class from all dropdowns
  function focusoutListener (event) {
    if (!document.activeElement.classList.contains('dropdown')) {
      dropdowns.forEach(dropdown => {
        dropdown.classList.remove(dropdownActiveClass)
      })
    }
  }
}())

And we have to update CSS:

.dropdown:hover .dropdown__wrapper,
.dropdown:focus-within .dropdown__wrapper,
.dropdown--active .dropdown__wrapper {
  width: 100%;
  height: auto;
  overflow: visible;
}

And then just enjoy this accessible dropdown! Full demo to play (use your Tab button):

Top comments (0)