City combobox
Type a few letters in any language, pick your city. Known cities come from WeDance's own cities table and filter instantly; a city we don't have yet is found through Google Places and added the moment it's picked. The saved value is always the canonical city — München, Мюнхен and munich all save as Munich.
Example
Try “Мюнх”, “mün”, “sao” — or a town we don't list yet.
Value: —
<Field label="Your city" :error="errors.city">
<CityCombobox
v-model="form.city"
placeholder="Munich, Berlin…"
v-bind="fieldAttrs('city', 'onb-city-error')"
/>
</Field>States
Behaviour
No free text: the value is always a canonical city. Known cities first, Google Places only for the rest.
- Focus an empty field → the biggest communities (“Popular on WeDance”).
- Typing filters the known cities instantly — accents and case don't matter, other spellings count (Мюнхен, Munique), the matched letters are bold.
- Fewer than 3 known matches and 2+ letters → after a 300 ms pause the server asks Google Places (cities only, English names). New cities show a + and the region; “powered by Google” appears under them.
- Picking a Google city adds it to WeDance's cities (English + local name, coordinates, the spelling you typed) and then sets the value — the next person finds it instantly.
- Typed text that wasn't picked reverts on blur. No match → “No city found. Check the spelling or try the English name.”
<!-- Store the id/slug, not just the name -->
<CityCombobox
v-model="form.city"
@select="city => { form.cityId = city.id; form.citySlug = city.slug }"
/>Value: —
<!-- Directory filter only (/cities): the typed text is the value.
Never on a field that is saved. -->
<CityCombobox
v-model="searchQuery"
free-text
:show-popular="false"
@select="city => navigateTo(`/cities/${city.slug}`)"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| v-model | string | '' | The canonical English city name (“Munich”) — only ever set by picking a city, never by typing. Text that was not picked reverts on blur; clearing the field clears the value. |
| @select | (city: CityOption) => void | — | Fires with the canonical city once it is picked (Google picks are resolved and saved first): { id, slug, name, localName, country, countryCode, region, lat, lng, placeId, altNames }. Store id or slug from here. |
| free-text | boolean | false | Search-box mode — the typed text is the value and Google is never asked. Only for filters (the /cities directory search), never for a saved field. |
| show-popular | boolean | true | On focus with an empty field, list the biggest WeDance communities. |
| limit | number | 8 | Max suggestions in the list. |
| debounce | number | 300 | ms between the last keystroke and asking the server (→ Google). Clamped to ≥ 250. Known cities filter instantly. |
| options | CityOption[] | — | Your own list instead of /api/city-options. Turns off the Google search (docs, tests, scoped lists). |
| placeholder | string | 'Search a city' | Hint inside the empty field. Keep the Field label. |
| size | 'default' | 'sm' | 'default' | Same heights as Input. |
| invalid | boolean | false | Red border + aria-invalid without a Field. |
| …attrs | HTML attrs | — | name, required, disabled, id, aria-* go to the <input>. |
Do & don't
Do — Use it wherever someone gives a city — profile, onboarding, ride origin, enquiry forms. Store the id or slug from @select where you can; a town we don't list yet is one Google pick away.
Don't — Use a plain text Input for a city, or free-text on a saved field. “München”, “Munich ” and “munich” become three cities nobody can group, count or link.
Accessibility
- ARIA 1.2 editable combobox:
role=comboboxon the input,aria-autocomplete=list,aria-expanded,aria-controls→ the listbox,aria-activedescendant→ the highlighted option. Focus never leaves the text box. - Keys: ↓/↑ move through suggestions (and open the list), Alt+↓ opens, Enter picks the highlighted city, Esc closes — a second Esc clears the field. Enter with nothing highlighted submits the form as usual.
- A polite live region announces how many cities are suggested, “searching”, “adding the city” and the no-match / error message. A city being added carries
aria-busy. - Inside a Field it picks up id, label, aria-describedby and aria-invalid like Input. Options are 40px+ tall for touch.