Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Component guides
  4. React Location Picker: Draggable Pin, Radius, Search
Auf Deutsch lesen
  1. Blog
  2. Component guides
  3. React Location Picker: Draggable Pin, Radius, Search
Component guides
Component guides

React Location Picker: Draggable Pin, Radius, Search

SR
Serban Rusu · Founder of Wingo UI
Oct 19, 2026 · 9 min read

On this page

0%
  1. What should a React location picker handle?
  2. Which React map libraries can you build a location picker on?
  3. What props does the Location Map need?
  4. How do I add a radius to the map?
  5. How do I search for an address?
  6. How does it behave on a phone?
  7. Is the location picker accessible?
  8. What happens when the map cannot load?
  9. What are the common location picker pitfalls?
  10. How do I add it to my project?
  11. FAQ
    1. How do I get the latitude and longitude when a user clicks a map in React?
    2. Why does Leaflet throw window is not defined in Next.js?
    3. What unit does a Leaflet circle radius use?
    4. Can I use OpenStreetMap tiles and Nominatim in production?
    5. Is the Wingo UI Location Map free?

TL;DR

A React location picker needs a pin you can click, drag and move with the keyboard, a search that flies the map to a place, and an optional radius circle for delivery zones or nearby filters. It also has to load the map library only in the browser, keep the page scroll working on phones, and show something useful when the tiles never arrive. The Wingo UI Location Map does all of this on Leaflet and OpenStreetMap with a controlled value and radius, plus a StaticMap card for emails and long lists.

Published Oct 19, 2026

You need a React location picker: a listing form that asks where the apartment is, a checkout that asks where to leave the parcel, a search page that filters by distance. A text field for the address works until the building has no number, the courier needs the back entrance, or the filter is "within 10 miles". Then you need a real map with a pin. This guide covers what a picker has to handle, the props that matter in the Wingo UI Location Map, and full examples for a pin, a radius and address search.

What should a React location picker handle?

Seven things. If you search for "pick location on map react", the typical answer is a react-leaflet useMapEvents click handler that drops a marker, which covers only the first one. Here is the list we hold our own react map picker to:

  1. Three ways to place the pin. Click or tap the map, drag the pin, and move it with the arrow keys once it has focus.
  2. Search. Type a town or an address, pick a result, and the map flies there.
  3. A radius for delivery zones and nearby filters, with a slider that resizes the circle live.
  4. My location, with an honest message when the browser refuses.
  5. A scroll that still works. A map that grabs the wheel or a one-finger swipe traps the reader halfway down the page.
  6. A server-safe load. Leaflet touches window on import, which crashes a Next.js server render.
  7. A fallback when tiles or the map chunk never arrive: offline, blocked, or simply not scrolled into view yet.

Points 5 and 7 are the ones you notice in production. A map in the middle of a form on a phone is a 300px area where a swipe pans the map instead of the page, and a map that fails shows a gray box with no idea where the pin was.

Which React map libraries can you build a location picker on?

Leaflet with OpenStreetMap needs no API key, while Google Maps and Mapbox need a key or token and an account. Facts are from each project's public docs, as of October 2026.

OptionWhat it isKey neededDraggable marker
react-leaflet (opens in a new tab)React components for Leaflet (v5 docs), Hippocratic License 2.1 (opens in a new tab)No for Leaflet itself; depends on your tile providerLeaflet's draggable marker option and dragend event
@vis.gl/react-google-maps (opens in a new tab)React components and hooks for the Google Maps JavaScript APIYes, Google says the platform requires an API key (opens in a new tab) for authentication and billingYes, through the Maps JavaScript API
Mapbox GL JS (opens in a new tab)Vector map library, used from React through wrappersYes, a Mapbox access tokendraggable: true and a dragend listener
Wingo UI Location MapPro source component on Leaflet 1.9 and OpenStreetMap tiles by defaultNo by default; tileUrl points at any raster providerBuilt in, plus click to place, keyboard moves and a radius

Google is the better choice when you need Google's place data and street-level address autocomplete worldwide, and you are fine with billing. Mapbox fits when you want vector styles. A react leaflet location picker is the lightest path to a pin on a map with no account, and that is what the Location Map is built on, so you get Leaflet's tiles without writing the click, drag, search and phone behavior yourself.

What props does the Location Map need?

A value and an onValueChange. The value is a GeoPoint ({ lat, lng }) or null for no pin yet. The map calls onValueChange after a drag ends, a click places the pin, a search result or the locate button moves it, or the keyboard rests.

bash
npx wingo-ui@latest add location-map

The CLI copies components/ui/location-map.tsx, the components and helpers it imports, and installs leaflet, motion and lucide-react.

Click the map to drop the pin, drag it onto another building, then move the radius slider: the circle and the towns count follow
$ npx wingo-ui@latest add location-map
ProLocation Map docs

The built-in search is offline and covers Romanian towns, areas and counties from ro-locations, which is why the demo above sits in Cluj-Napoca. For a US app, pass a center and a geocoder that knows your country:

tsx
"use client";
import { useState } from "react";
import {
LocationMap,
nominatimGeocoder,
nominatimReverseGeocoder,
type GeoPoint,
} from "@/components/ui/location-map";
// both helpers share one queue: at most one Nominatim request a second
const geocoder = nominatimGeocoder({ email: "dev@example.com", countryCodes: "us" });
const reverseGeocoder = nominatimReverseGeocoder({ email: "dev@example.com" });
const NEW_YORK: GeoPoint = { lat: 40.7128, lng: -74.006 };
export function DropOffLocation() {
const [point, setPoint] = useState<GeoPoint | null>(null);
return (
<LocationMap
value={point}
onValueChange={setPoint}
center={NEW_YORK}
defaultZoom={12}
geocoder={geocoder}
reverseGeocoder={reverseGeocoder}
showTownsInRadius={false}
locale="en-US"
labels={{ search: "Search an address in New York" }}
/>
);
}

nominatimGeocoder defaults countryCodes to "ro", so set it (or pass "" for the whole world). The reverse geocoder names the pin with a street and town 500ms after it stops moving. Without one, the place line falls back to the nearest Romanian town, and outside Romania it shows only the coordinates.

The other props you will reach for: defaultZoom (13 shows a neighborhood, 16 a street), draggable and clickToPlace (both on by default), readOnly for a store page that only shows where the store is, searchable and locate to hide the search field or the my-location button, and height (320px by default).

How do I add a radius to the map?

Pass a radius in kilometers. A radius map React apps use for delivery zones or "listings nearby" is one more controlled value: radius and onRadiusChange, with radiusRange and radiusStep for the slider under the map. null or 0 hides both the circle and the slider.

tsx
"use client";
import { useState } from "react";
import { LocationMap, type GeoPoint } from "@/components/ui/location-map";
import { Button } from "@/components/ui/button";
export function DeliveryZone() {
const [point, setPoint] = useState<GeoPoint | null>({ lat: 40.7128, lng: -74.006 });
const [radius, setRadius] = useState<number | null>(5);
async function save() {
if (!point || !radius) return;
await fetch("/api/zones", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ lat: point.lat, lng: point.lng, radiusKm: radius }),
});
}
return (
<div className="flex flex-col gap-4">
<LocationMap
value={point}
onValueChange={setPoint}
radius={radius}
onRadiusChange={setRadius}
radiusRange={[1, 25]}
radiusStep={1}
defaultZoom={11}
showTownsInRadius={false}
locale="en-US"
/>
<Button tone="brand" onClick={save} disabled={!point || !radius}>
Save zone
</Button>
</div>
);
}

onRadiusChange fires on every step of the slider, and the view fits the circle when you let go. If you draw the zone yourself with plain Leaflet, remember that L.Circle takes meters, not kilometers. showTownsInRadius adds a "14 towns in range" count from the Romanian town list, so turn it off elsewhere. The Slider under the map is the same one you use in forms.

How do I search for an address?

Pass a geocoder: a function that takes the query and { signal, locale } and returns GeocodeResult[] (id, label, optional description, point, optional bounds). The map debounces typing, aborts the previous request through the signal, and fits the view to bounds when a result has them. The results list is the Search Input with arrow keys and Enter.

nominatimGeocoder is a good prototype. OpenStreetMap's Nominatim usage policy (opens in a new tab) allows at most one request per second and rules out client-side autocomplete, and the helper queues requests to one a second, but a search field that queries as you type is still the use case the policy warns against. For production, point the geocoder at your own route, which can call any provider, cache the answers and keep the key on the server:

ts
import type { GeocodeResult, Geocoder } from "@/components/ui/location-map";
export const placesGeocoder: Geocoder = async (query, { signal, locale }) => {
const params = new URLSearchParams({ q: query, lang: locale });
const response = await fetch(`/api/places?${params}`, { signal });
if (!response.ok) throw new Error(`places ${response.status}`);
return (await response.json()) as GeocodeResult[];
};

A geocoder that throws shows "No places found" instead of an error. If you also collect a written address, the pin and the form fields complement each other: the Address Input holds the street and number, the map holds the exact spot. The Romanian address format guide shows that form part by part, and the React combobox guide covers the keyboard rules the results list follows.

How does it behave on a phone?

The inline map does not take the page scroll. With interactive="auto" (the default) the map is live in place with a mouse, while on touch a tap opens a full-height sheet with the search on top, a map you can pinch, drag and tap, and Done at the bottom. Closing the sheet any other way discards the change, so a stray swipe never moves the pin. Inline, a phone shows at most 220px of map, since the full one is a tap away.

interactive="always" keeps the map live in place everywhere, and interactive="expand" always opens the sheet (a dialog on desktop), which suits a compact form row. The sheet is the Drawer; the React bottom sheet guide explains why sheets beat popovers below 768px. On desktop, scrollWheelZoom="focus" lets the wheel zoom only after you clicked into the map and stops when the pointer leaves, so scrolling past the map never gets stuck on it.

Is the location picker accessible?

Yes, with a keyboard path for every move. Tab reaches the search, the map (arrows pan, plus and minus zoom), the pin (arrows move it 10px, Shift and the arrows 50px, committed a moment after you stop), the map buttons and the slider. Screen readers get the map described by the place name and coordinates under it and hear every move as "Pin moved: ..." from a live region. Every string, announcements included, lives in labels and merges over DEFAULT_LOCATION_MAP_LABELS, so you translate it without forking the file. Reduced motion drops the pin's drop animation and the map's animated flights.

What happens when the map cannot load?

A drawn static card takes its place: a neighborhood sketch with the pin, the radius ring and a chip with the place and coordinates. You see it before the map loads (with lazy on, Leaflet loads only once the map is within 200px of the viewport), when the browser is offline, when more than six tiles fail in a row, and when the map chunk fails. Leaflet is imported from an effect, never at module scope, so the component renders on the server without next/dynamic.

staticOnly skips the map entirely. For an email, a print view, a thumbnail or a list of fifty stores, use StaticMap on its own: it needs no map library and no network.

The same picker with staticOnly on: the drawn card with the pin, the radius ring, the place and the coordinates, with no tiles requested
$ npx wingo-ui@latest add location-map
ProLocation Map docs
tsx
import { StaticMap } from "@/components/ui/location-map";
export function OrderMap() {
return (
<StaticMap
value={{ lat: 40.7128, lng: -74.006 }}
place="350 Broadway, New York"
height={180}
tone="brand"
locale="en-US"
/>
);
}

What are the common location picker pitfalls?

Four mistakes show up once a picker leaves the demo.

  • Shipping on the public OSM tiles. The tile usage policy (opens in a new tab) has no SLA and can block heavy use. Set tileUrl to your provider's raster tiles and attribution to the credit it requires.
  • Romanian defaults left on. Without props the map centers on Cluj-Napoca, searches Romanian places offline, counts Romanian towns in the radius and nominatimGeocoder limits results to "ro". A US app sets center, geocoder with countryCodes, reverseGeocoder and showTownsInRadius={false}, as in the examples above.
  • Saving on every slider step. onRadiusChange fires per step; save on submit or debounce it.
  • Dark mode without a plan. darkTiles="filter" inverts the light tiles in dark mode with no second provider; "url" swaps to darkTileUrl, and "none" keeps light tiles in both themes.

How do I add it to my project?

Run npx wingo-ui@latest add location-map. The Location Map is a Pro component (pricing); the Search Input and Drawer it builds on are free. More single-component deep dives are in the component guides category, and the React form components guide applies the same touch, keyboard and screen reader rules to every form control.

Components in this post

  • Location Map

    A React location map to pick or show a place: a draggable pin, an optional radius with a towns count, offline search and a static fallback.

    Pro

FAQ

How do I get the latitude and longitude when a user clicks a map in React?

In react-leaflet you read the click with the useMapEvents hook and store e.latlng in state. The Wingo UI Location Map does this for you: a click places the pin and onValueChange receives { lat, lng }.

Why does Leaflet throw window is not defined in Next.js?

Leaflet reads window when the module is imported, so importing it in code that runs on the server crashes the render. Load it only in the browser, either with next/dynamic and ssr: false from a client component or with a dynamic import inside an effect, which is what the Location Map does.

What unit does a Leaflet circle radius use?

Leaflet's L.Circle takes its radius in meters. The Location Map's radius prop is in kilometers and converts it for you.

Can I use OpenStreetMap tiles and Nominatim in production?

Only for light use. The OSM tile policy gives no SLA and can block heavy traffic, and the Nominatim policy allows at most one request per second and no client-side autocomplete, so production apps should point tileUrl and the geocoder at their own provider.

Is the Wingo UI Location Map free?

No, the Location Map is a Pro component. The Search Input and Drawer it builds on are free.

  • Maps
  • Location
  • Leaflet
  • Mobile UI
  • React

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Component guides

Embla Carousel React: Accessible Slides, No Layout Shift

Build an embla carousel react component for photos, listings, testimonials and onboarding, with keyboard controls, pausable autoplay and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Component guides

React Animated Number Counter: Rolling Odometer Digits

Build a React animated number counter that rolls each digit like an odometer, takes its separators from Intl, holds its width and respects reduced motion.

SRSerban Rusu·Oct 19, 2026·9 min read
Component guides

React Audio Player with Waveform: Scrub, Seek, Peaks

Build a React audio player with waveform scrubbing for podcasts, chat voice notes and call logs, with keyboard seek, precomputed peaks and no layout shift.

SRSerban Rusu·Oct 19, 2026·9 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI