Skip to content
Componentsbeta

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: —

vue
<Field label="Your city" :error="errors.city">
  <CityCombobox
    v-model="form.city"
    placeholder="Munich, Berlin…"
    v-bind="fieldAttrs('city', 'onb-city-error')"
  />
</Field>

States

Tell us your city so we can fill your plan.

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.”
vue
<!-- Store the id/slug, not just the name -->
<CityCombobox
  v-model="form.city"
  @select="city => { form.cityId = city.id; form.citySlug = city.slug }"
/>

Value: —

vue
<!-- 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

PropTypeDefaultDescription
v-modelstring''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-textbooleanfalseSearch-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-popularbooleantrueOn focus with an empty field, list the biggest WeDance communities.
limitnumber8Max suggestions in the list.
debouncenumber300ms between the last keystroke and asking the server (→ Google). Clamped to ≥ 250. Known cities filter instantly.
optionsCityOption[]—Your own list instead of /api/city-options. Turns off the Google search (docs, tests, scoped lists).
placeholderstring'Search a city'Hint inside the empty field. Keep the Field label.
size'default' | 'sm''default'Same heights as Input.
invalidbooleanfalseRed border + aria-invalid without a Field.
…attrsHTML 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=combobox on 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.