A composable that fetches suggestions from /v1/autocomplete, and a component that uses it. Use a publishable key whose allowed origins include your site. Works in Vue 3 and Nuxt.
The composable
// useGeolithAutocomplete.ts
import { ref, watch, type Ref } from 'vue';
export interface Place {
type: 'Feature';
geometry: { type: 'Point'; coordinates: [number, number] };
properties: { gid: string; label: string; postalcode?: string; locality?: string; country_code?: string };
}
export function useGeolithAutocomplete(text: Ref<string>, apiKey: string) {
const places = ref<Place[]>([]);
/** The label last picked; typing it back does not search again. */
const picked = ref('');
let timer: ReturnType<typeof setTimeout> | undefined;
let controller: AbortController | undefined;
watch(text, (value) => {
clearTimeout(timer);
controller?.abort();
if (value.trim().length < 3 || value === picked.value) {
places.value = [];
return;
}
timer = setTimeout(async () => {
controller = new AbortController();
const url = new URL('https://api.geolith.app/v1/autocomplete');
url.searchParams.set('text', value);
url.searchParams.set('size', '5');
url.searchParams.set('api_key', apiKey);
try {
const res = await fetch(url, { signal: controller.signal });
if (res.ok) places.value = ((await res.json()) as { features: Place[] }).features;
} catch (error) {
if ((error as Error).name !== 'AbortError') throw error;
}
}, 200);
});
return { places, picked };
}
The component
<!-- AddressAutocomplete.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useGeolithAutocomplete, type Place } from './useGeolithAutocomplete';
const emit = defineEmits<{ select: [place: Place] }>();
const text = ref('');
const { places, picked } = useGeolithAutocomplete(text, import.meta.env.VITE_GEOLITH_KEY);
function pick(place: Place) {
picked.value = place.properties.label;
text.value = place.properties.label;
places.value = [];
emit('select', place);
}
</script>
<template>
<div>
<input v-model="text" type="search" placeholder="Start typing an address" autocomplete="off" />
<ul role="listbox">
<li v-for="place in places" :key="place.properties.gid" role="option" :aria-selected="false">
<button type="button" @click="pick(place)">{{ place.properties.label }}</button>
</li>
</ul>
<small>© OpenStreetMap contributors (ODbL), Overture Maps Foundation</small>
</div>
</template>
Put the key in .env.local as VITE_GEOLITH_KEY=gc_pk_…. In Nuxt, read it from useRuntimeConfig().public instead of import.meta.env.