Building a location picker often means giving users two ways to select an address: typing it or clicking on the map. In this tutorial, we'll combine both approaches using MapLibre GL and Geoapify to create a smooth "search or click" UX.
Try the live demo:
β‘οΈ View on CodePen
APIs used:
- Address Autocomplete API - real-time suggestions as users type
- Reverse Geocoding API - convert map clicks to addresses
- Map Tiles API - vector map styles for MapLibre GL
- Map Marker Icon API - custom marker icons
What you'll build:
- A MapLibre GL map with Geoapify vector tiles
- An address autocomplete field that flies to the selected location
- Click-to-address functionality using reverse geocoding
- A reusable marker that updates for both interactions
π§ Table of Contents
- Set Up a MapLibre GL Map
- Add the Geoapify Address Autocomplete Field
- Sync Autocomplete Selection with the Map
- Add Reverse Geocoding on Map Click
- Explore the Demo
- Summary
- FAQ
Step 1: Set Up a MapLibre GL Map
Start by loading MapLibre GL JS and initializing a map with Geoapify vector tiles.
Include MapLibre GL
Add the CSS and JavaScript from a CDN:
<!-- MapLibre GL CSS -->
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" />
<!-- MapLibre GL JS -->
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
Create the map container
<div id="map"></div>
Style it to fill the viewport:
html, body {
margin: 0;
height: 100%;
width: 100%;
}
#map {
height: 100%;
width: 100%;
}
Initialize the map
Create a new MapLibre map using a Geoapify style.json URL:
const myAPIKey = "YOUR_API_KEY";
const map = new maplibregl.Map({
container: "map",
style: `https://maps.geoapify.com/v1/styles/osm-bright-grey/style.json?apiKey=${myAPIKey}`,
center: [-77.0234, 38.9088], // Washington DC
zoom: 12,
maxZoom: 20
});
// Add navigation controls
map.addControl(new maplibregl.NavigationControl());
The style URL points to Geoapify's vector map tiles. You can choose from different styles like osm-bright, dark-matter, positron, and others. See the Map Tiles documentation for the full list.
π API Key: Sign up at geoapify.com to get a free API key.
A MapLibre GL map initialized with Geoapify vector tiles.
Step 2: Add the Geoapify Address Autocomplete Field
Next, we'll add an address search field that suggests locations as the user types.
Include the Geocoder Autocomplete library
<!-- Geoapify Geocoder Autocomplete CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@geoapify/geocoder-autocomplete@3.0.1/styles/minimal.css" />
<!-- Geoapify Geocoder Autocomplete JS -->
<script src="https://cdn.jsdelivr.net/npm/@geoapify/geocoder-autocomplete@3.0.1/dist/index.min.js"></script>
The library comes with several built-in themes: minimal, minimal-dark, round-borders, and round-borders-dark.
Create the autocomplete container
Position the autocomplete field over the map:
<div class="autocomplete-panel">
<div id="autocomplete" class="autocomplete-container"></div>
</div>
.autocomplete-panel {
position: absolute;
top: 10px;
left: 10px;
width: 400px;
z-index: 1002;
}
Initialize the autocomplete widget
const autocompleteInput = new autocomplete.GeocoderAutocomplete(
document.getElementById("autocomplete"),
myAPIKey,
{ /* options */ }
);
The GeocoderAutocomplete widget handles API calls and renders the dropdown automatically.
The autocomplete dropdown appears as the user types, showing matching address suggestions.
Step 3: Sync Autocomplete Selection with the Map
When the user selects an address, we want to:
- Move the map to that location
- Place a marker at the coordinates
Listen to the select event
let marker;
autocompleteInput.on("select", (location) => {
// Remove existing marker
if (marker) {
marker.remove();
}
if (location) {
// Create a new marker at the selected location
marker = new maplibregl.Marker({
element: createMarkerIcon(),
offset: [0, -25]
})
.setLngLat([location.properties.lon, location.properties.lat])
.addTo(map);
// Fly to the selected location
map.flyTo({
center: [location.properties.lon, location.properties.lat],
zoom: 14
});
}
});
The location.properties object contains the coordinates (lon, lat) and the formatted address.
Create a custom marker icon
Use the Geoapify Map Marker Icon API to generate a custom pin:
function createMarkerIcon() {
const img = document.createElement("img");
img.src = `https://api.geoapify.com/v2/icon/?type=awesome&color=%23ff5b5f&size=50&scaleFactor=2&apiKey=${myAPIKey}`;
img.style.width = "38px";
img.style.height = "55px";
return img;
}
The scaleFactor=2 renders the icon at double resolution for sharp display on retina screens.
π‘ Marker offset: The
offset: [0, -25]shifts the marker so that the pin's tip (not its center) aligns with the coordinates. The offset is calculated as-(icon height - shadow offset) / 2.
After selecting an address, the map flies to the location and displays a marker.
Step 4: Add Reverse Geocoding on Map Click
Now let's add the second interaction: clicking on the map to get the address at that point. This uses the Geoapify Reverse Geocoding API.
Listen to map clicks
map.on("click", function (e) {
const lat = e.lngLat.lat;
const lon = e.lngLat.lng;
// Call reverse geocoding for the clicked location
getAddressByLatLon(lat, lon).then((location) => {
if (marker) {
marker.remove();
}
// Set the address in the autocomplete input
autocompleteInput.setValue(location.properties.formatted);
// Place a marker at the returned coordinates
marker = new maplibregl.Marker({
element: createMarkerIcon(),
offset: [0, -25]
})
.setLngLat([location.properties.lon, location.properties.lat])
.addTo(map);
});
});
Implement the reverse geocoding function
function getAddressByLatLon(lat, lon) {
return fetch(
`https://api.geoapify.com/v1/geocode/reverse?lat=${lat}&lon=${lon}&apiKey=${myAPIKey}`
)
.then((result) => result.json())
.then((result) => {
if (result && result.features && result.features.length) {
return result.features[0];
}
return null;
});
}
The Reverse Geocoding API takes latitude and longitude coordinates and returns the nearest address. The response includes properties.formatted (the full address string) and individual components like street, city, and country.
π Learn more: See the full API reference in the Reverse Geocoding documentation.
Clicking anywhere on the map triggers reverse geocoding. The address fills the input field and a marker appears.
Step 5: Explore the Demo
The live CodePen demo shows both interactions working together.
Try these flows
- Type an address β Select from dropdown β Map flies to location with marker
- Click on the map β Address appears in input field β Marker placed at click point
- Switch between both β Notice how one marker and one input are reused
Theme switcher
The demo includes a theme selector that switches both the autocomplete style and the map tiles between light and dark modes:
const mapStyles = {
light: `https://maps.geoapify.com/v1/styles/osm-bright-grey/style.json?apiKey=${myAPIKey}`,
dark: `https://maps.geoapify.com/v1/styles/dark-matter-brown/style.json?apiKey=${myAPIKey}`
};
Where to use this pattern
This "search or click" UX works well for:
- Delivery forms - let users type their address or drop a pin on their exact location
- Location pickers - allow users to choose meeting points or drop-off locations
- Real estate apps - search for properties or explore by clicking on the map
- Onboarding flows - capture user location during account setup
π Try it in the interactive demo:
Summary
You've built a location picker with two interaction methods:
- Address autocomplete - type to search, select to fly to location
- Reverse geocoding - click the map to get the address at that point
Both interactions share a single marker and input field, creating a consistent, intuitive UX.
Useful links:
FAQ
Q: How do I restrict autocomplete results to a specific country?
A: Pass a filter option when initializing the autocomplete: { filter: { countrycode: "us" } }. See the filter documentation for more options.
Q: Can I use a different marker icon?
A: Yes. You can use any HTML element as a marker: an <img>, <div>, or inline SVG. The Marker Icon API offers customizable pins with different colors, shapes, and icons.
Q: How do I prevent marker clicks from triggering the map click handler?
A: Check if the click originated from a marker element:
map.on("click", (e) => {
if (e.originalEvent.target.closest(".maplibregl-marker")) return;
// ... your reverse geocoding logic
});
Q: What happens if reverse geocoding returns no results?
A: This can happen for clicks in oceans or remote areas. Check for empty results and handle gracefully:
if (!result || !result.features || !result.features.length) {
console.log("No address found at this location");
return;
}
Q: Can I use this with React or Vue?
A: Yes. The autocomplete library works with any framework. There's also a dedicated React package. For MapLibre, use the standard JS library or community wrappers like react-map-gl.
Q: How do I change the map style dynamically?
A: Use map.setStyle(newStyleUrl). Note that you may need to re-add event listeners after a style change. Use map.once("style.load", ...) to know when the new style is ready.
Q: What's the difference between forward and reverse geocoding?
A: Forward geocoding converts an address (text) to coordinates. Reverse geocoding converts coordinates to an address. In this tutorial, autocomplete uses forward geocoding, and map clicks use reverse geocoding.
Try It Now
π Open the Live Demo
Sign up at geoapify.com and get your free API key to start building interactive location pickers.




Top comments (0)