autocomplete search

Implementing an Autocomplete Feature with Keyboard Navigation and Accessibility in Mind

Learn how to implement an autocomplete feature with keyboard navigation and accessibility in mind.

We understand how important it is to create an inclusive and user-friendly autocomplete feature. In this blog post, I will guide you through the process step by step, from creating the HTML structure to adding JavaScript functionality and testing for both functionality and accessibility. By suggesting options as users type, you can greatly enhance their experience. And by ensuring accessibility for all users, you can create a truly inclusive feature.

The example below involves the search autocomplete for two city inputs (fromCity and toCity)

HTML Structure

The first step is to create the HTML structure for the autocomplete feature. This typically involves using an input field and a container to display the suggestions. The input field will capture the user’s input, while the container will dynamically display the suggestions based on the input. This code includes separate inputs for “From City” and “To City” along with separate lists for autocomplete suggestions.

<div class="autocomplete">
  <div class="wrapper">
    <label for="fromCity">From City: </label>
    <input type="text" id="fromCity" class="city-input" aria-autocomplete="list" role="combobox" aria-owns="autocomplete-from-list" autocomplete="off">
    <ul id="autocomplete-from-list" class="autocomplete-list" role="listbox" aria-labelledby="fromCity"></ul>
  </div>
  <div class="wrapper">
    <label for="toCity">To City:</label>
    <input type="text" id="toCity" class="city-input" aria-autocomplete="list" role="combobox" aria-owns="autocomplete-to-list" autocomplete="off">
    <ul id="autocomplete-to-list" class="autocomplete-list" role="listbox" aria-labelledby="toCity"></ul>
  </div>
</div>

This implementation includes ARIA attributes for accessibility, such as role, aria-autocomplete, aria-owns, and aria-labelledby. The keyboard navigation is handled using event listeners for keydown events. Also, I added autocomplete=”off” to disable the browser’s autocomplete feature. It makes sense to disable browser auto-complete, as it interferes with keyboard navigation.

Setting autocomplete="off" should prevent the browser from providing autocomplete suggestions for those specific input fields. Keep in mind that some browsers may not fully respect this attribute, but it generally works in modern browsers.

CSS Styling

Next, we need to apply CSS styling to the autocomplete feature to make it visually appealing and user-friendly. This includes styling the input field, suggestions container, and individual suggestion items. Additionally, we can add CSS animations to enhance the user experience.

.autocomplete {
  display: flex;
  margin-bottom: 20px;
  gap: 15px;
}

.wrapper {
  position: relative;
  display: flex;
  flex-direction: column;
}

.city-input {
  width: 300px;
  margin-bottom: 10px;
  padding: 8px;
}

.autocomplete-list {
  position: absolute;
  top: 100%;
  z-index: 1;
  list-style: none;
  padding: 0;
  margin: 0;
  box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
  background-color: #fff;
  width: 300px;
  border-radius: 6px;
  max-height: 150px;
  overflow-y: auto;
}

.autocomplete-list li {
  padding: 8px 16px;
  cursor: pointer;
}

.autocomplete-list li:hover {
  background-color: #f0f0f0;
}

.autocomplete-list li.selected {
  background-color: #007bff;
  color: #fff;
}

JavaScript Functionality

Now, let’s add the JavaScript functionality to our autocomplete feature. I will use an event listener to capture the user’s input and fetch suggestions from a data source. The suggestions will be filtered based on the input and displayed in the suggestions container. Additionally, we will implement keyboard navigation and accessibility features.

const fromCityInput = document.getElementById("fromCity");
const toCityInput = document.getElementById("toCity");
const fromCityList = document.getElementById("autocomplete-from-list");
const toCityList = document.getElementById("autocomplete-to-list");

const cities = [
  "New York",
  "Los Angeles",
  "Chicago",
  "Houston",
  "Phoenix",
  "Philadelphia",
  "San Antonio",
  "San Diego",
  "Dallas",
  "San Jose"
];

fromCityInput.addEventListener("input", function () {
  const value = fromCityInput.value.toLowerCase();
  const filteredCities = cities.filter((city) =>
    city.toLowerCase().includes(value)
  );

  updateList(filteredCities, fromCityList);
});

toCityInput.addEventListener("input", function () {
  const value = toCityInput.value.toLowerCase();
  const filteredCities = cities.filter((city) =>
    city.toLowerCase().includes(value)
  );

  updateList(filteredCities, toCityList);
});

function updateList(filteredCities, list) {
  list.innerHTML = "";

  filteredCities.forEach((city) => {
    const li = document.createElement("li");
    li.textContent = city;
    li.setAttribute("role", "option");
    li.addEventListener("click", function () {
      if (list === fromCityList) {
        fromCityInput.value = city;
      } else {
        toCityInput.value = city;
      }
      list.innerHTML = "";
    });
    list.appendChild(li);
  });
}

function handleKeyDown(e, input, list) {
  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
    e.preventDefault();

    const selected = document.querySelector(`#${list.id} li.selected`);

    if (selected) {
      selected.classList.remove("selected");
    }

    let next;

    if (e.key === "ArrowDown") {
      next = selected ? selected.nextElementSibling : list.firstElementChild;
    } else {
      next = selected ? selected.previousElementSibling : list.lastElementChild;
    }

    if (next) {
      next.classList.add("selected");
      input.value = next.textContent;
    }
  } else if (e.key === "Enter") {
    const selected = document.querySelector(`#${list.id} li.selected`);
    if (selected) {
      input.value = selected.textContent;
      list.innerHTML = "";
    }
  }
}

fromCityInput.addEventListener("keydown", function (e) {
  handleKeyDown(e, fromCityInput, fromCityList);
});

toCityInput.addEventListener("keydown", function (e) {
  handleKeyDown(e, toCityInput, toCityList);
});

document.addEventListener("click", function (e) {
  if (
    !e.target.matches(".city-input") &&
    !e.target.matches(".autocomplete-list li")
  ) {
    fromCityList.innerHTML = "";
    toCityList.innerHTML = "";
  }
});

Here you can see the complete code and functionality.

Testing and Accessibility

After implementing the autocomplete feature, it is important to thoroughly test it to ensure its functionality and accessibility. Test various scenarios, such as typing different inputs, navigating through suggestions using the keyboard, and selecting a suggestion using the Enter key. Additionally, ensure that the autocomplete feature is accessible by using proper HTML semantics, providing ARIA attributes, and testing with assistive technologies.

Conclusion

Implementing an autocomplete feature with keyboard navigation and accessibility in mind can greatly enhance the user experience. By following the steps outlined in this blog post, you can create a user-friendly autocomplete feature that provides suggestions as the user types and is accessible to all users. Remember to test the feature thoroughly and make any necessary adjustments to ensure its functionality and accessibility.