Someone starts typing an address into your form. The suggestions list is empty. Should the helper text ask them to keep typing, add a ZIP code, or check what they entered?
An empty array doesn't answer that. Address autocomplete in API 2.0.0 includes a reason field so your interface can give a useful next step. That small distinction belongs in a larger form design: keep the text someone entered, know whether they selected a suggestion, and make sure an old search cannot replace a newer one.
For an existing field, the form.js autocomplete tutorial handles the suggestions and keyboard selection. The rest of this guide explains the states to preserve when building a custom form directly against the API. The request examples are scoped to US addresses.
Give an empty list a useful next step
For a US address form scoped to Denver, a request early in typing might be the following. This is a request excerpt; use your usual authentication, and select the API 2.0.0 contract with the header shown.
GET /address?q=m&city=Denver&state=CO&country=US
Parse-Version: 2.0.0
The response includes this excerpt:
{
"q": "m",
"reason": "more_input",
"addresses": []
}
There's no suggestion to display yet. "Keep typing your street address" is enough. Use the other reasons to choose similarly small hints:
| Response | What the form can say | What the person can do |
|---|---|---|
more_input with no addresses | "Keep typing your street address." | Add more street text. |
missing_context with no addresses | "Add a ZIP code, or a city and state." | Give the search a location. |
no_matches with no addresses | "No suggestions found. You can enter the address yourself." | Check the spelling or continue manually. |
reason: null with addresses | A short suggestion count | Choose a suggestion or keep typing. |
HTTP 503, service_unavailable | "Suggestions are temporarily unavailable. You can keep typing." | Continue manually or try again. |
The reason is part of the autocomplete response. You don't need deep=true to receive it. Treat reason as an optional, open string: older responses may omit it, and a future value shouldn't break the form. When the addresses array is empty and you don't recognize the reason, a generic empty-list message is a useful fallback.
Check the HTTP status before interpreting the body as a successful search. A failed request must not be turned into addresses: [] with no_matches; that would tell the person a search completed when it didn't. Keep a service failure separate from an empty result, while leaving the address editable in both cases.
These states describe the search. no_matches doesn't establish that an address doesn't exist, and receiving suggestions doesn't establish postal deliverability. If an application needs a separate delivery decision, it needs evidence for that decision beyond a selected autocomplete row.
Send the location the person actually chose
A street fragment can be useful in many places. If your form already has a ZIP code, pass it as postal. If it has a city and state, pass them as city and state. Keep country explicit when the form is scoped to one country.
For example, a city-and-state search could use:
GET /address?q=12%20Main&city=Denver&state=CO&country=US
Parse-Version: 2.0.0
That is a request example, not a claim that this particular address will produce a match. Use the Address API reference for the accepted parameters and response fields.
Collect enough usable context before sending it. An empty street field doesn't need a request. A half-entered ZIP should remain editable instead of being sent as if it were complete. A UI can debounce typing to avoid starting a lookup on every keystroke, but it should still honor the API's more_input response rather than assume one character count works for every search.
Location also belongs in the identity of a pending request. If someone changes the ZIP while keeping the same street text, the old suggestions belong to the old search. Close that list immediately and clear any selected match associated with it. Then search with the new context when it is ready.
The built-in data-parse="address" field accepts a city and state or ZIP in the same input. A custom form with separate location fields must explicitly send those values and react when they change; adding the attribute doesn't automatically connect every other field in the form.
Keep entered text separate from a selected match
There are three useful pieces of state: the current text and location, the suggestions for that input, and the record the person explicitly selected. They have different lifetimes.
Typing changes the first. A response can replace the second. Only a click or an explicit keyboard choice should set the third. Simply highlighting the first row or moving focus out of the field should not silently commit it.
This distinction matters when a person selects a suggestion and then edits the street. The selected record no longer describes the visible field. Clear it, along with any hidden coordinates or identifiers your application copied from it. Keep the newly typed text. If a city or ZIP field contains a later manual edit, don't erase that edit merely because it was also an output of an earlier selection.
One way to handle copied values is to track which fields the autocomplete filled. Clear or replace a copied value only while it still belongs to that selection. As soon as the person edits the field, treat its value as theirs. The same rule protects an apartment number someone adds while a request is in flight.
Preserve the distinction between the displayed text and the returned evidence, too. A suggestion can describe a street with number: null. A UI may retain a house number the person typed to make a useful street line, but that does not turn the returned null into a confirmed house number. Keep the original match intact if you retain it alongside the form values.
At submission, the editable address fields should represent what the person is submitting. A selected record is additional context, not a reason to replace those fields with an older snapshot. As with any browser-supplied value, handle it according to your application's server-side rules.
Prevent an old response from reopening the list
Debouncing reduces requests. It doesn't establish which response is allowed to update the form.
Suppose the person types a street in Denver, pauses long enough to start a request, then changes the city. The first request may complete after the second. It may also fail after the second succeeds. Either result belongs to the old input and should be ignored.
Use a revision counter that changes whenever the search becomes obsolete. Capture the query and location with the revision, and check them again before applying either a success or a failure. Here is the guard in isolation:
let revision = 0;
async function search() {
const mine = ++revision;
const request = readCurrentInput();
const stillCurrent = () =>
mine === revision &&
sameInput(request, readCurrentInput());
try {
const result = await lookup(request);
if (!stillCurrent()) return;
showResult(result);
} catch (error) {
if (!stillCurrent()) return;
showUnavailable();
}
}
function invalidateSearch() {
revision += 1;
closeSuggestions();
}
This is a state-management excerpt, not a complete API client. Call invalidateSearch() immediately when text or location changes, even if the next search is still waiting on its debounce timer. Invalidate on reset and when the person dismisses the list as well. Otherwise, a request started before Escape can reopen the list just after they closed it.
Abort an obsolete fetch when you can, and retain the revision check. Cancellation and the right to update the current form solve different parts of the problem. The same guard must protect loading indicators, error messages, and suggestion click handlers; an old completion shouldn't clear a new request's loading state or select a record from a removed list.
Make selection work with a keyboard
An editable combobox gives the input and its suggestion list a shared interaction model. Keep a real label on the input, connect it to a listbox with aria-controls, and update aria-expanded as the list opens and closes. With focus staying in the input, aria-activedescendant identifies the highlighted option.
Arrow keys should move through suggestions. Enter accepts an actively highlighted suggestion. Escape closes the list while preserving the text. Tab should continue through the form without forcing a selection. Leave ordinary editing keys to the browser. The WAI-ARIA combobox pattern describes these semantics and keyboard behaviors.
Announce concise search status through a polite live region, including when suggestions are unavailable. Avoid moving focus just to deliver that message. Wait for text composition to finish before searching or intercepting Enter, so an input method can finish composing the person's text.
Keyboard checks are part of a useful test, along with touch and assistive-technology checks in the browsers you support. A dropdown that can only be selected with a mouse is an unfinished address field.
Try the states without an API request
Download the offline address-form demo, save the HTML file, and open it in a browser. It contains the markup, styles, and JavaScript in one file. Its fictional Exampleville suggestions and simulated delay make the transitions visible; it doesn't make network requests or need a key.
Start with 12 Ex in the street field. Select a suggestion with the arrow keys and Enter, then edit the street and watch the selection clear. Clear the location context to see the missing-context state. Switch the simulated response to no matches or a temporary failure and use Preview values to inspect the text you can still submit. The address-details field remains yours throughout.
To exercise the stale-response guard, edit the street or location while the "Looking for suggestions" message is visible. Only the current search may fill the list. Escape and Tab dismiss pending suggestions as well as visible ones. The fixture deliberately lets its old timers finish, so the revision check has work to do.
The demo uses a small local character threshold and fictional records to exercise the interface. Those are fixture choices, not API search rules or real address results. Replace its simulated lookup with your own integration while preserving the separation between entered values, suggestions, and a selected match. People should be able to finish entering an address even when autocomplete has nothing useful to add.