DEV Community

Alex
Alex

Posted on

3 1

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):

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay