Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Media

Location Map

Media

Location Map React component for Tailwind CSS v4

Image LightboxPDF Viewer
Live props
State
defaultRadiusNone
defaultZoom13
readOnly
Behavior
radiusStep1km
draggable
clickToPlace
lazy
staticOnly
interactive
scrollWheelZoom
Layout
showRadiusSlider
searchable
locate
height320px
Content
showTownsInRadius
attribution
locale
Appearance
tileUrl
darkTiles
darkTileUrl
tone
color
Code
import { LocationMap } from "@/components/ui/location-map"

<LocationMap />
  1. Home
  2. Components
  3. Media
  4. Location Map
Pro

Location Map React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

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.

With a mouse the map is live in place: drag to pan, double-click or use the round + and − buttons to zoom, and the wheel zooms only after you clicked into the map, so scrolling the page never gets stuck on it. Click anywhere to drop the pin there (it falls in with a small squash and a ripple) or drag the pin itself; it lifts while you hold it and the circle follows. Type in the search field to find a town, an area or a county offline (an online geocoder finds street addresses); arrows move through the results, Enter picks one and the map flies there. The locate button asks for your position and moves the pin; a refusal is shown under the map. With a radius set, the slider under the map resizes the circle live, shows how many towns fall inside, and fits the view to the circle when you let go. On a phone the inline map does not steal the page scroll: tap it and a full-height sheet opens with the search on top, a fully interactive map (pinch, drag, tap to place) and Done at the bottom; closing the sheet any other way discards the changes. Keyboard: Tab reaches the search, the map (arrows pan, + and − zoom), the pin (arrows move it 10px, Shift+arrows 50px, committed a moment after you stop), the map buttons and the slider. Screen readers get the map as an application described by the place name and coordinates under it, and hear every move. Without network or before the map loads, a drawn static card shows the pin, the place and the coordinates.

Location Map is a Pro React component in the React media components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add location-map, together with Button, Drawer, Listbox, Search Input, Skeleton and 11 more and the npm packages leaflet, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add location-map

Location Map is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import LocationMap from @/components/ui/location-map.

import { useState } from "react"
import { LocationMap, type GeoPoint } from "@/components/ui/location-map"

export function ListingLocation() {
  const [point, setPoint] = useState<GeoPoint | null>({ lat: 46.77, lng: 23.5857 })
  const [radius, setRadius] = useState<number | null>(5)
  return (
    <LocationMap
      value={point}
      onValueChange={setPoint}
      radius={radius}
      onRadiusChange={setRadius}
      locale="en-US"
      labels={{
        search: "Search a town",
        towns: (count, formatted) => (count === 1 ? "1 town nearby" : `${formatted} towns nearby`),
      }}
    />
  )
}

Props

  • value
    GeoPoint | null

    The pin, controlled: { lat, lng }. null shows no pin until one is placed.

  • defaultValuedefault "null"
    GeoPoint | null

    The pin while uncontrolled. The demo starts at Str. Memorandumului 28, Cluj-Napoca.

  • onValueChange
    (value: GeoPoint | null) => void

    After a drag ends, a click places the pin, a search result or the locate button moves it, or the keyboard rests.

  • radius
    number | null

    The radius in km, controlled. null or 0 hides the circle and the slider.

  • defaultRadiusdefault 0
    number | null

    The radius in km while uncontrolled; 0 (or null) means no circle and no slider.

  • onRadiusChange
    (radius: number | null) => void

    Every step of the slider.

  • radiusRangedefault "[1, 50]"
    LocationMapRange ([min, max] in km)

    The slider's bounds in km.

  • radiusStepdefault 1
    number

    The slider's step in km.

  • showRadiusSliderdefault true
    boolean

    The slider row under the map, while a radius is set and the map is not read-only.

  • showTownsInRadiusdefault true
    boolean

    Adds "14 towns in range" to the radius row, counted from ro-locations (0 outside Romania).

  • center
    GeoPoint

    The first view, and a new view when it changes. Defaults to the value, then Cluj-Napoca (46.7712, 23.6236).

  • zoom
    number

    The zoom level, controlled (1 to 19). The inline map and the sheet share it.

  • defaultZoomdefault 13
    number

    The zoom level while uncontrolled: 13 shows a neighbourhood, 10 a county, 16 a street.

  • onZoomChange
    (zoom: number) => void

    After every zoom, from the buttons, the wheel, a pinch or a flight.

  • draggabledefault true
    boolean

    The pin can be dragged, and moved with the arrow keys once focused.

  • clickToPlacedefault true
    boolean

    A click or a tap on the map moves the pin there.

  • readOnlydefault false
    boolean

    Shows the pin and the circle only: no search, no slider, no locate, the pin stays put. The map still pans and zooms.

  • searchabledefault true
    boolean

    The search field above the map.

  • geocoderdefault "roLocationsGeocoder"
    (query: string, options: { signal: AbortSignal; locale: string }) => Promise<GeocodeResult[]>

    What the search asks. roLocationsGeocoder finds Romanian towns, areas and counties offline; nominatimGeocoder({ email }) finds street addresses through OpenStreetMap (one request a second, debounced 700ms). The previous request is aborted.

  • reverseGeocoder
    (point: GeoPoint, options: { signal: AbortSignal; locale: string }) => Promise<GeocodeResult | null>

    Names the pin after it moves ("Str. Memorandumului 28, Cluj-Napoca"), 500ms after it rests. Without it the nearest town from ro-locations names it. nominatimReverseGeocoder({ email }) is ready to use.

  • locatedefault true
    boolean

    The my-location button. Hidden where the browser has no geolocation; a refusal is shown and announced.

  • heightdefault 320
    number

    The map's height in px. Phones show at most 220px inline, since the full map is a tap away.

  • tileUrldefault "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
    string

    The raster tiles. OpenStreetMap's public tiles are for light use; a production app should point this at its own tile provider.

  • attributiondefault "© OpenStreetMap"
    string

    The credit in the corner that the tile provider requires. Linked to the OpenStreetMap copyright page for OSM tiles.

  • darkTilesdefault "filter"
    "filter" | "url" | "none"

    Dark mode tiles. filter inverts the light tiles with a hue rotation (no second provider needed), url swaps to darkTileUrl, none keeps light tiles in both themes.

  • darkTileUrldefault ""
    string

    The tiles for dark mode when darkTiles is "url", e.g. a dark raster style of your provider.

  • lazydefault true
    boolean

    Loads Leaflet (42 KB) only once the map is within 200px of the viewport. Until then the static card shows.

  • staticOnlydefault false
    boolean

    Never loads the map: the drawn static card with the pin, the place and the coordinates. For emails, prints, thumbnails and long lists.

  • interactivedefault "auto"
    "auto" | "always" | "expand"

    auto: live in place with a mouse, tap to open a full-height sheet on touch (the page keeps scrolling over the inline map). always: live in place everywhere. expand: a click or tap always opens the sheet (a dialog on desktop).

  • scrollWheelZoomdefault "focus"
    "focus" | "always" | "never"

    focus: the wheel zooms only after the map was clicked or focused, and stops when the pointer leaves. always and never do what they say.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The pin, the circle, the slider and the sheet's Done button.

  • colordefault ""
    string

    Any CSS color; replaces the tone for the pin, the circle and the slider.

  • localedefault ""
    string

    Coordinates, distances and the geocoder's language. Empty uses the app's locale, then the browser's.

  • setApi
    (api: LocationMapApi) => void

    Called once with { flyTo(point, zoom?), fitRadius(), locate(): Promise<GeoPoint | null>, getMap(): L.Map | null }. The calls go to the sheet's map while it is open.

  • labels
    Partial<LocationMapLabels>

    Every string, English by default (DEFAULT_LOCATION_MAP_LABELS): search, noResults, locate, locating, locateDenied, locateFailed, zoomIn, zoomOut, expand, done, radius(km, formatted), radiusLabel, towns(count, formatted), pin, map, sheetTitle, offline, placePin, tapToOpen, near(town, distance), coordinates(lat, lng), moved(place).

  • classNames
    Partial<Record<"search" | "map" | "canvas" | "controls" | "control" | "attribution" | "hint" | "radius" | "slider" | "radiusLabel" | "summary" | "error" | "static" | "sheet", string>>

    Extra classes for the inner parts.

  • className
    string

    Extra classes for the root, merged last.

Parts

  • StaticMap

    component

    The fallback card alone, no map library and no network: a drawn neighbourhood with the pin, an optional radius ring and a chip with the place and coordinates. Props: { value?: GeoPoint | null; place?: ReactNode; radius?: number | null; offline?: boolean; height?: number; tone?: Tone; color?: string; locale?: string; labels?: Partial<LocationMapLabels> }.

  • roLocationsGeocoder

    constant

    The offline default: Romanian cities, areas and counties from ro-locations, diacritics ignored. Type: Geocoder.

  • nominatimGeocoder

    function

    OpenStreetMap address search. countryCodes defaults to "ro", endpoint to nominatim.openstreetmap.org; requests wait for their turn so there is at most one a second. Signature: (options?: { email?: string; countryCodes?: string; endpoint?: string; limit?: number }) => Geocoder.

  • nominatimReverseGeocoder

    function

    The street and town under a point, "Strada Memorandumului 28, Cluj-Napoca", in the map's locale. Signature: (options?: { email?: string; endpoint?: string }) => ReverseGeocoder.

  • DEFAULT_LOCATION_MAP_LABELS

    constant

    Every English string of the map: search, locate, zoom, radius and towns count, the sheet, offline and pin announcements. Spread it and override what you translate.

Examples

Nearby filter

A 25 km radius around Bucharest; the towns inside come from citiesWithin and follow the slider.

Online address search

nominatimGeocoder finds street addresses (one request a second), the reverse geocoder names the pin.

Read-only store location

readOnly shows the pin only; a reverseGeocoder turns the point into an address line.

Dark tiles

Switch to dark mode: "filter" inverts the light tiles, "none" keeps them light.

Tap to expand

interactive="expand": the map opens in a full-height sheet (a dialog on desktop) and Done commits the pin.

Static card in an email

StaticMap needs no map library and no network: emails, prints and long lists.

Tones and colors

The pin and the radius take any tone or CSS color.

Dependencies

leafletmotionlucide-reactbuttondrawerlistboxsearch-inputskeletonslidertooltipuse-controllable-stateuse-debounceuse-localeuse-media-querymotionnumber-formatro-locationstonesui-config

Files

  • components/ui/location-map.tsx

FAQ

Is the React Location Map component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Location Map?

Run npx wingo-ui@latest add location-map. It also adds the Wingo UI items it builds on (Button, Drawer, Listbox, Search Input, Skeleton and 11 more) and the npm packages leaflet, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/location-map.

How does Location Map behave on phones?

On a phone the inline map does not steal the page scroll: tap it and a full-height sheet opens with the search on top, a fully interactive map (pinch, drag, tap to place) and Done at the bottom; closing the sheet any other way discards the changes.

See also

Romanian locationslibFilter panelSearch InputDrawer

Used in

Listing Detail PageAppointment Booking PageRestaurant Page

More React media components

Audio PlayerExcel ViewerDOCX ViewerCSV ViewerDOCX EditorFile System
All 17 React media components

Articles using this component

  • Nextjs Real Estate Template: Search, Map and Mortgage

    Build it: screens and apps · Nov 6, 2026 · 10 min read

  • Nextjs Restaurant Template: Open Now, Menu, Book a Table

    Build it: screens and apps · Nov 6, 2026 · 10 min read

  • React Location Picker: Draggable Pin, Radius, Search

    Component guides · Oct 19, 2026 · 9 min read

PreviousImage LightboxNext PDF Viewer
New listing · Step 3 of 5

Where is the apartment?

Move the pin onto the building. The exact address only shows once you confirm a viewing.

Cluj-Napoca46.7700° N, 23.5857° E
Radius 5 km · 1 town in range

Cluj-Napoca · 46.7700° N, 23.5857° E

value 46.7700, 23.5857 · radius 5 km · step 1 · zoom 13 · interactive auto · wheel focus · drag on · click on · lazy on · dark filter