Address Autocomplete — Search-As-You-Type HTML CSS JS

Address Autocomplete · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Debounced search-as-you-type
A 220ms setTimeout debounce avoids filtering (or fetching) on every single keystroke.
Match highlighting
The matched substring in each suggestion is wrapped in a styled <mark> so users see why it matched.
Full keyboard navigation
ArrowUp/Down, Enter, and Escape behave like a native combobox, with scrollIntoView keeping the highlighted item visible.
No-results state
An empty-state message shows the typed query when nothing matches, instead of a blank dropdown.
Selection confirmation chip
A green checkmark chip confirms the chosen address and hides automatically if the query is edited afterward.
Clear button
A ✕ button resets the input, hides the confirmation, and returns focus to the field.
Outside-click dismissal
Clicking anywhere outside the widget closes the suggestion list, the same pattern used by this library's dropdowns and popovers.
Animation-safe dropdown
Opacity and transform-only transitions keep the suggestion list smooth after every framework export.

About this UI Snippet

Address Autocomplete — Debounced Search, Highlighted Matches & Keyboard Navigation

Screenshot of the Address Autocomplete snippet rendered live

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:

text
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

  1. 1
    Paste HTML, CSS, and JSAn address input with a pin icon appears. Type at least one character to trigger suggestions.
  2. 2
    Watch the debounced filterAfter a short pause in typing, a dropdown shows up to 6 matches with the typed text highlighted in each one.
  3. 3
    Navigate with the keyboardPress ArrowDown/ArrowUp to highlight a suggestion, Enter to select it, or Escape to dismiss the list.
  4. 4
    Select an addressClick a suggestion or press Enter — the input fills in and a green confirmation chip shows the full address.
  5. 5
    Clear and start overClick the ✕ button to clear the input, hide the confirmation, and refocus the field.
  6. 6
    Swap 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

Checkout and shipping forms
Speed up address entry at checkout — pair with a country selector for the country field.
Sign-up and profile forms
Autocomplete home or billing addresses during onboarding instead of five separate manual fields.
Store and service locators
Let users type a starting address before searching nearby locations on a map.
Real-estate and rental search
A property search bar where typing a neighborhood, street, or zip filters listings live.
Delivery and logistics apps
Confirm a precise drop-off address with the same highlighted-match, keyboard-friendly pattern.
Learning combobox patterns
A practical reference for debouncing, ARIA-free keyboard navigation, and outside-click dismissal you can reuse in any autocomplete input.

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.