How to use keyboard arrows to navigate in the dropdown

I have a simple dropdown and I want to have the possibility that the user can navigate via keyboard arrows (up/down) in the dropdown.

Here is the code:

function myFunction() {
  document.getElementById("myDropdown").classList.toggle("show");
}

window.onclick = function(event) {
  if (!event.target.matches('.dropbtn')) {
    var dropdowns = document.getElementsByClassName("dropdown-content");
    var i;
    for (i = 0; i < dropdowns.length; i++) {
      var openDropdown = dropdowns[i];
      if (openDropdown.classList.contains('show')) {
        openDropdown.classList.remove('show');
      }
    }
  }
}
.dropbtn {
  background-color: #3498DB;
  color: white;
  padding: 16px;
  font-size: 16px;
  border: none;
  cursor: pointer;
}

.dropbtn:hover, .dropbtn:focus {
  background-color: #2980B9;
}

.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-content {
  display: none;
  position: absolute;
  background-color: #f1f1f1;
  min-width: 160px;
  overflow: auto;
  box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  z-index: 1;
}

.dropdown-content a {
  color: black;
  padding: 12px 16px;
  text-decoration: none;
  display: block;
}

.dropdown a:hover {background-color: #ddd;}

.show {display: block;}
<div class="dropdown">
  <button onclick="myFunction()" class="dropbtn">Dropdown</button>
  <div id="myDropdown" class="dropdown-content">
    <a href="#home">Home</a>
    <a href="#about">About</a>
    <a href="#contact">Contact</a>
  </div>
</div>

This works fine when I am using tab, but I want to use arrows (up/down), can anybody try to help me with this?

5 thoughts on “How to use keyboard arrows to navigate in the dropdown”

  1. You could write a function that listens for keystrokes (when the dropdown is open), determine the key and change the focus accordingly.

    Reply
  2. Mihalma!

    So, by scrounging around on the internet (keywords "HTML5 keyboard navigable dropdown"), I found this: https://dev.to/emmabostian/creating-a-custom-accessible-drop-down-3gmo

    You can use a element around your dropdown.

    <select>
    <option value="">Value 1</option>
    <option value="">Value 2</option>
    <option value="">Value 3</option>
    <option value="">Value 4</option>
    </select>
    

    Sadly, this makes for some pretty trashy looking styling, which is what the rest of the article is for.

    Reply
  3. function myFunction() {
      let i = 0; // iterate over children elements inside dropdown
      const dropdown = document.getElementById("myDropdown");
      const childs = dropdown.children; // get all dropdown elements
      dropdown.classList.toggle("show");
      // attach keyboard events
      window.addEventListener("keydown", event => {
        switch(event.code) {
          case "ArrowDown":
            for (let c of childs) 
              c.classList.remove('dropbtn-selected')
            childs[Math.abs(i) % childs.length].classList.add('dropbtn-selected');
             i++;
            break;
          case "ArrowUp":
            for (let c of childs) 
              c.classList.remove('dropbtn-selected')
            childs[Math.abs(i) % childs.length].classList.add('dropbtn-selected');
             i--;
            break;
        }
      if (event.isComposing || event.keyCode === 229) {
        return;
      }
      });
    }
    .dropbtn {
      background-color: #3498DB;
      color: white;
      padding: 16px;
      font-size: 16px;
      border: none;
      cursor: pointer;
    }
    .dropbtn-selected {
      background-color: #2980B9;
    }
    .dropbtn:hover, .dropbtn:focus {
      background-color: #2980B9;
    }
    
    .dropdown {
      position: relative;
      display: inline-block;
    }
    
    .dropdown-content {
      display: none;
      position: absolute;
      background-color: #f1f1f1;
      min-width: 160px;
      overflow: auto;
      box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
      z-index: 1;
    }
    
    .dropdown-content a {
      color: black;
      padding: 12px 16px;
      text-decoration: none;
      display: block;
    }
    
    .dropdown a:hover {background-color: #ddd;}
    
    .show {display: block;}
    <div class="dropdown">
      <button onclick="myFunction()" class="dropbtn">Dropdown</button>
      <div id="myDropdown" class="dropdown-content">
        <a href="#home">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </div>
    </div>

    Don’t forget to remove eventListener as soon as you select a dropdown option and close

    Reply
  4. just add keydown event on dropdown ,play with the focus and cancel the scrolling

    var pos = 0;
    var maxpos = 0;
    function myFunction() {
       pos=0;
       document.getElementById("myDropdown").classList.toggle("show");
       maxpos = $("#myDropdown a").length - 1;
        var x = window.scrollX, y = window.scrollY;
        $("#myDropdown a").eq(pos).trigger("focus");
        window.scrollTo(x, y);
    }
    
    $("#myDropdown, .dropbtn").on("keydown", function(e){
      if(e.which == 40){//down 
        pos = pos == maxpos ? 0 : pos + 1;
        $("#myDropdown a").eq(pos).trigger("focus");
      }
      if(e.which == 38){//up 
        pos = pos == 0 ? maxpos : pos - 1;
        $("#myDropdown a").eq(pos).trigger("focus");
      }
      return false;//cancel scrolling
    });
    
    
    window.onclick = function(event) {
      if (!event.target.matches('.dropbtn')) {
         var dropdowns = document.getElementsByClassName("dropdown-content");
        var i;
        for (i = 0; i < dropdowns.length; i++) {
          var openDropdown = dropdowns[i];
          if (openDropdown.classList.contains('show')) {
            openDropdown.classList.remove('show');
          }
        }
      }
    }
    .dropbtn {
      background-color: #3498DB;
      color: white;
      padding: 16px;
      font-size: 16px;
      border: none;
      cursor: pointer;
    }
    
    .dropbtn:hover, .dropbtn:focus {
      background-color: #2980B9;
    }
    
    .dropdown {
      position: relative;
      display: inline-block;
    }
    
    .dropdown-content {
      display: none;
      position: absolute;
      background-color: #f1f1f1;
      min-width: 160px;
      overflow: auto;
      box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
      z-index: 1;
    }
    
    .dropdown-content a {
      color: black;
      padding: 12px 16px;
      text-decoration: none;
      display: block;
    }
    
    .dropdown a:hover, .dropdown a:focus{
    background-color: #ddd;
    }
    
    .show {display: block;}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="dropdown">
      <button onclick="myFunction()" class="dropbtn">Dropdown</button>
      <div id="myDropdown" class="dropdown-content">
        <a href="#home">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </div>
    </div>
    Reply
  5. I would add the listeners to the dropdown and its content, rather than the entire page.

    You can call focus() on the previous/next child by storing a selected index using the drop-down’s dataset property.

    const
      ARROW_UP = 38,
      ARROW_DOWN = 40;
    
    const mod = (n, m) => ((n % m) + m) % m;
    
    const navigateList = e => {
      const
        dropdown = e.target.closest('.dropdown'),
        selectedIndex = parseInt(dropdown.dataset.selectedIndex, 10),
        children = dropdown.querySelectorAll('.dropdown-content a');
    
      switch (e.which) {
        case ARROW_UP:
          focusDropdownChild(dropdown, mod(selectedIndex - 1, children.length));
          break;
        case ARROW_DOWN:
          focusDropdownChild(dropdown, mod(selectedIndex + 1, children.length));
          break;
      }
    };
    
    const toggleShow = (e) => {
      const
        button = e.target,
        dropdown = button.closest('.dropdown'),
        content = dropdown.querySelector('.dropdown-content');
      content.classList.toggle('show');
      focusDropdownChild(dropdown, 0);
    };
    
    const navigate = e => {
      const
        item = e.target,
        content = item.closest('.dropdown-content');
      console.log(`Navigating to... "${item.textContent}"`);
      content.classList.toggle('show');
      e.preventDefault();
      e.stopImmediatePropagation();
    };
    
    const focusDropdownChild = (dropdown, index) => {
      const children = dropdown.querySelectorAll('.dropdown-content a');
      children.forEach(child => child.classList.remove('dropdown-item-focus'));
      dropdown.dataset.selectedIndex = index;
      children[index].focus();
      children[index].classList.add('dropdown-item-focus');
    };
    
    const connectListeners = (dropdown) => {
      const
        button = dropdown.querySelector('.dropdown-button'),
        content = dropdown.querySelector('.dropdown-content'),
        children = content.querySelectorAll('a');
      button.addEventListener('click', toggleShow);
      content.addEventListener('keydown', navigateList);
      children.forEach(child => child.addEventListener('click', navigate));
    };
    
    const disconnectListeners = (dropdown) => {
      const
        button = dropdown.querySelector('.dropdown-button'),
        content = dropdown.querySelector('.dropdown-content'),
        children = content.querySelectorAll('a');
      button.removeEventListener('click', toggleShow);
      content.removeEventListener('keydown', navigateList);
      children.forEach(child => child.removeEventListener('click', navigate));
    };
    
    document.querySelectorAll('.dropdown').forEach(connectListeners);
    .dropdown-button {
      background-color: #3498DB;
      color: white;
      padding: 16px;
      font-size: 16px;
      border: none;
      cursor: pointer;
    }
    
    .dropdown-button:hover,
    .dropdown-button:focus {
      background-color: #2980B9;
    }
    
    .dropdown {
      position: relative;
      display: inline-block;
    }
    
    .dropdown-content {
      display: none;
      position: absolute;
      background-color: #f1f1f1;
      min-width: 160px;
      overflow: auto;
      box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
      z-index: 1;
    }
    
    .dropdown-content a {
      color: black;
      padding: 12px 16px;
      text-decoration: none;
      display: block;
    }
    
    .dropdown a:hover {
      background-color: #ddd;
    }
    
    .show {
      display: block
    }
    
    .dropdown-item-focus {
      background: yellow;
    }
    <div class="dropdown">
      <button class="dropdown-button">Dropdown</button>
      <div class="dropdown-content">
        <a href="#home">Home</a>
        <a href="#about">About</a>
        <a href="#contact">Contact</a>
      </div>
    </div>
    Reply

Leave a Comment