Facebook Pixel

Building an Autocomplete Search Bar: A Complete Guide

Autocomplete with debounce, keyboard navigation, and API calls. Here is the complete implementation.

Building an Autocomplete Search Bar: A Complete Guide

The autocomplete search bar is one of the most common machine coding questions. Here is the complete implementation.

Requirements

  • Input field that shows suggestions as the user types.
  • Debounce to avoid excessive API calls.
  • Keyboard navigation (up/down/enter/escape).
  • Highlight matching text.
  • Click outside to close.
  • Loading state.
  • No results state.

HTML

<div class="autocomplete" id="autocomplete"> <input type="text" id="searchInput" placeholder="Search..." autocomplete="off" /> <ul class="suggestions" id="suggestions"></ul> </div>

JavaScript

const input = document.getElementById("searchInput"); const suggestions = document.getElementById("suggestions"); let currentHighlight = -1; let debounceTimer; function debounce(fn, delay) { return function (...args) { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => fn(...args), delay); }; } async function fetchSuggestions(query) { if (!query) { suggestions.innerHTML = ""; suggestions.style.display = "none"; return; } suggestions.innerHTML = '<li class="loading">Loading...</li>'; suggestions.style.display = "block"; // simulate API call const data = await fetchData(query); renderSuggestions(data, query); } function renderSuggestions(items, query) { if (items.length === 0) { suggestions.innerHTML = '<li class="no-results">No results found</li>'; return; } suggestions.innerHTML = items.map((item, i) => ` <li class="suggestion-item" data-index="${i}" onclick="selectSuggestion('${item}')"> ${highlightMatch(item, query)} </li> `).join(""); } function highlightMatch(text, query) { const regex = new RegExp(`(${query})`, "gi"); return text.replace(regex, "<strong>$1</strong>"); } function selectSuggestion(item) { input.value = item; suggestions.innerHTML = ""; suggestions.style.display = "none"; } const debouncedFetch = debounce(fetchSuggestions, 300); input.addEventListener("input", (e) => { currentHighlight = -1; debouncedFetch(e.target.value); }); input.addEventListener("keydown", (e) => { const items = suggestions.querySelectorAll(".suggestion-item"); if (e.key === "ArrowDown") { e.preventDefault(); currentHighlight = Math.min(currentHighlight + 1, items.length - 1); } if (e.key === "ArrowUp") { e.preventDefault(); currentHighlight = Math.max(currentHighlight - 1, 0); } if (e.key === "Enter" && currentHighlight >= 0) { e.preventDefault(); items[currentHighlight].click(); } if (e.key === "Escape") { suggestions.style.display = "none"; } items.forEach((item, i) => item.classList.toggle("highlighted", i === currentHighlight)); }); document.addEventListener("click", (e) => { if (!document.getElementById("autocomplete").contains(e.target)) { suggestions.style.display = "none"; } });

CSS

.autocomplete { position: relative; width: 300px; } #searchInput { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } .suggestions { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ccc; border-top: none; list-style: none; margin: 0; padding: 0; max-height: 200px; overflow-y: auto; z-index: 100; display: none; } .suggestion-item { padding: 10px; cursor: pointer; } .suggestion-item:hover, .suggestion-item.highlighted { background: #f0f0f0; } .loading, .no-results { padding: 10px; color: #888; } strong { color: #007bff; }

The Takeaway

Autocomplete: HTML (input + suggestions list), JavaScript (debounce, fetch, render, keyboard navigation, highlight, click outside to close), CSS (absolute positioning, hover, highlighted states). Key features: debounce (300ms), arrow keys, enter to select, escape to close, highlight matching text, loading and no-results states.

Create an input and a suggestions list. Debounce the input (300ms). Fetch suggestions on input. Render filtered results. Add keyboard navigation (ArrowUp/Down, Enter, Escape). Highlight matching text. Close on click outside. Show loading and no-results states.

To avoid making an API call on every keystroke. Debounce delays the fetch until the user stops typing for 300ms. This reduces API calls, improves performance, and provides a better user experience.

Track a currentHighlight index. ArrowDown increments, ArrowUp decrements. Enter triggers the highlighted item's click. Escape closes the suggestions. Toggle a 'highlighted' CSS class on the active item.

Use a regex to wrap the matching part in a <strong> tag: text.replace(new RegExp(`(${query})`, 'gi'), '<strong>$1</strong>'). Style strong with a different color to highlight the match.

Add a document click listener. Check if the click target is inside the autocomplete container: if (!autocomplete.contains(e.target)) suggestions.style.display = 'none'. This closes the suggestions when clicking anywhere else.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.