More Forms Snippets
Address Autocomplete — Search-As-You-Type HTML CSS JS
Address Autocomplete · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Address Autocomplete — Debounced Search, Highlighted Matches & Keyboard Navigation

Address autocomplete is one of the highest-value form upgrades you can make: typing a full street address by hand is slow and error-prone, while a filtered suggestion list lets users confirm an address in two keystrokes. This snippet builds the complete pattern — without a paid geocoding API — over a static address list, ready to swap for a real provider.
Debounced filtering
Every keystroke clears a pending setTimeout and schedules a new one 220ms out, so the filter only runs once typing pauses rather than on every single character. This is the standard debounce pattern for search-as-you-type: it keeps the UI responsive while avoiding wasted filter passes (or, with a real API, wasted network requests) on every keystroke.
Substring highlighting
highlight() finds the query's position in each candidate's text with indexOf and wraps the matching slice in a <mark> tag, styled here as a colored, bold span rather than the default yellow highlight. This lets users see *why* each suggestion matched, which builds trust in the list faster than an unhighlighted result.
Full keyboard navigation
Arrow Down/Up move an activeIndex through the visible <li> items, toggling an .active class and calling scrollIntoView({ block: 'nearest' }) so the highlighted item never leaves the dropdown's visible area on a long list. Enter selects the active item (or does nothing if none is highlighted, avoiding an accidental selection), and Escape closes the dropdown without selecting — mirroring native <select> and combobox behavior so keyboard users get an experience they already know.
Selection state and confirmation
Selecting an address (by click or Enter) fills the input with the address's main line, reveals a green confirmation chip showing the full address, and shows a clear ("✕") button. Typing again after a selection hides the confirmation chip immediately, so the UI never shows a stale "confirmed" state next to an edited query.
Animation and dismissal
The dropdown fades and slides in with opacity/transform only (never height), and closes the same way before being hidden via hidden after the transition — keeping it crash-safe across every export target. A document-level click listener closes the list when you click anywhere outside the .aac-wrap container, the same outside-click pattern used by dropdown menus and popovers throughout this library.
Accessibility and a real production geocoder
This demo filters a small static array client-side, which is enough to show the interaction pattern but not enough for real addresses worldwide. A production build swaps the array filter for a debounced fetch to a geocoding provider — Google Places, Mapbox, or Loqate all expose an autocomplete endpoint that takes a partial query and returns ranked suggestions — while keeping the exact same debounce timing, highlight rendering, and keyboard handling described above. For accessibility, add role="combobox" to the input, role="listbox" to the suggestion list, and aria-activedescendant pointing at the currently highlighted option's id, so screen reader users get the same "which suggestion is focused" feedback that sighted keyboard users already see via the .active class.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the keyboard state machine by hand — paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how activeIndex, currentMatches, and the debounce timer interact so that arrow keys, mouse hover, and Enter never fall out of sync with each other. The same assistant is useful for optimizing it — asking whether 220ms is the right debounce delay for a real geocoding API call versus this static array filter, and whether the highlight function's simple indexOf approach needs to change to handle multi-word or reordered query matches. It's just as good for extending the widget: ask it to add role="combobox" and aria-activedescendant for full screen-reader support, wire it to a real provider like Google Places or Mapbox with the same render function, or add a "use my current location" button using the Geolocation API. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a debounced "address autocomplete" input in plain HTML, CSS, and JavaScript — no external geocoding API required for this version (filter a static in-memory array), but structured so a real API call could be dropped in later.
Requirements:
- A text input that, on every keystroke, clears any pending debounce timer and starts a new one (around 200ms) before filtering — so filtering (or in production, a network request) only happens once typing pauses, not on every character.
- A filtering function that matches the query against a list of address objects (each with a main line and a sub line) case-insensitively, limited to a small number of results (e.g. 6), and renders a "no results" message when nothing matches instead of an empty dropdown.
- Each rendered suggestion must highlight the exact substring that matched the query by wrapping it in a mark element, computed via a simple case-insensitive indexOf, not a regex-based fuzzy match.
- Full keyboard navigation: ArrowDown/ArrowUp move an active-item index through the currently rendered suggestions (clamped to the list bounds, not wrapping), Enter selects the active suggestion (doing nothing if no suggestion is active), and Escape closes the dropdown without selecting. Whenever the active index changes, scroll that item into view within the dropdown using scrollIntoView with block: nearest.
- Selecting a suggestion (by click or Enter) must fill the input with the address's main line, show a separate confirmation element with a checkmark and the full address text, and reveal a clear button; editing the input again after a selection must immediately hide the confirmation element.
- The dropdown must animate in and out using only opacity and transform (never height or a display toggle mid-transition), and must close whenever a click happens anywhere outside the widget's wrapping container.Step by step
How to Use
- 1Paste HTML, CSS, and JSAn address input with a pin icon appears. Type at least one character to trigger suggestions.
- 2Watch the debounced filterAfter a short pause in typing, a dropdown shows up to 6 matches with the typed text highlighted in each one.
- 3Navigate with the keyboardPress ArrowDown/ArrowUp to highlight a suggestion, Enter to select it, or Escape to dismiss the list.
- 4Select an addressClick a suggestion or press Enter — the input fills in and a green confirmation chip shows the full address.
- 5Clear and start overClick the ✕ button to clear the input, hide the confirmation, and refocus the field.
- 6Swap in a real geocoding APIReplace the ADDRESSES array filter with a fetch to a provider (Google Places, Mapbox, Loqate) inside the debounce timeout, keeping the same render(matches, query) call.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Replace the ADDRESSES.filter() call inside the debounce timeout with a fetch to your provider's autocomplete endpoint, map its response into { main, sub } objects, and pass them to the existing render(matches, query) function — the highlighting, keyboard navigation, and selection logic all stay the same.
Add role="combobox" and aria-expanded to the input, role="listbox" to the <ul>, role="option" plus aria-selected to each <li>, and an aria-activedescendant on the input pointing at the active item's id — update these alongside the existing activeIndex logic.
Animating height or using display toggles directly causes dropdowns to snap instead of glide once converted to a framework's utility classes. Sticking to opacity and transform (translateY) guarantees a smooth open/close in every export target, including Tailwind.
Filter the ADDRESSES array (or your API request) by a country/region field before applying the text match — add a country dropdown above the input and pass its value into the filter condition.
In React, track the query, matches, and activeIndex in useState and debounce with a useRef timer inside useEffect; in Vue, use ref()/watch(); in Angular, use a component method with a debounce via RxJS. The highlight, keyboard handling, and outside-click logic port directly.