Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Location Picker
Location picker dialog with address search, interactive map placeholder, coordinates display, and recent locations
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Pin your location precisely. This React location picker dialog provides address search with autocomplete suggestions, visual map selection area, latitude/longitude coordinate display, and saved locations history. Built with shadcn/ui Dialog, Input, Button, ScrollArea, and Badge components using Tailwind CSS, users select locations with multiple input methods and visual confirmation. Search address, select on map, view coordinates—perfect for delivery apps, event planning, property listings, or any Next.js application where precise location selection improves service delivery and user experience.
"use client";import { useState } from "react";import { MapPin, Search, Navigation, Clock, Star, Check, Crosshair, Copy, Trash2,} from "lucide-react";import { Button } from "@/components/ui/button";import { Badge } from "@/components/ui/badge";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { ScrollArea } from "@/components/ui/scroll-area";import { Separator } from "@/components/ui/separator";import { cn } from "@/lib/utils";type Location = { id: string; name: string; address: string; lat: number; lng: number; type?: "recent" | "saved";};const mockSuggestions: Location[] = [ { id: "1", name: "Empire State Building", address: "350 5th Ave, New York, NY 10118", lat: 40.7484, lng: -73.9857 }, { id: "2", name: "Central Park", address: "New York, NY 10022", lat: 40.7829, lng: -73.9654 }, { id: "3", name: "Times Square", address: "Manhattan, NY 10036", lat: 40.7580, lng: -73.9855 },];const savedLocations: Location[] = [ { id: "home", name: "Home", address: "123 Main Street, Brooklyn, NY 11201", lat: 40.6892, lng: -73.9857, type: "saved" }, { id: "work", name: "Work", address: "456 Park Ave, New York, NY 10022", lat: 40.7614, lng: -73.9776, type: "saved" },];const recentLocations: Location[] = [ { id: "r1", name: "Coffee Shop", address: "789 Broadway, New York, NY 10003", lat: 40.7308, lng: -73.9973, type: "recent" }, { id: "r2", name: "Gym", address: "321 Fitness Blvd, New York, NY 10001", lat: 40.7501, lng: -73.9972, type: "recent" },];export const title = "React Dialog Block Location Picker";export default function DialogLocationPicker() { const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const [selectedLocation, setSelectedLocation] = useState<Location | null>(null); const [showSuggestions, setShowSuggestions] = useState(false); const [isLocating, setIsLocating] = useState(false); const [copied, setCopied] = useState(false); const filteredSuggestions = search.length > 2 ? mockSuggestions.filter( (loc) => loc.name.toLowerCase().includes(search.toLowerCase()) || loc.address.toLowerCase().includes(search.toLowerCase()) ) : []; const handleSearchFocus = () => { if (search.length > 2) { setShowSuggestions(true); } }; const handleSearchChange = (value: string) => { setSearch(value); setShowSuggestions(value.length > 2); }; const handleSelectLocation = (location: Location) => { setSelectedLocation(location); setSearch(location.address); setShowSuggestions(false); }; const handleUseCurrentLocation = () => { setIsLocating(true); // Simulate geolocation setTimeout(() => { const currentLocation: Location = { id: "current", name: "Current Location", address: "Your current location", lat: 40.7128, lng: -74.0060, }; setSelectedLocation(currentLocation); setSearch("Current Location"); setIsLocating(false); }, 1500); }; const handleCopyCoordinates = () => { if (selectedLocation) { navigator.clipboard.writeText(`${selectedLocation.lat}, ${selectedLocation.lng}`); setCopied(true); setTimeout(() => setCopied(false), 1500); } }; const handleConfirm = () => { if (selectedLocation) { console.log("Location selected:", selectedLocation); setOpen(false); } }; const handleClose = () => { setOpen(false); setTimeout(() => { setSearch(""); setSelectedLocation(null); setShowSuggestions(false); setCopied(false); }, 200); }; return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <MapPin className="mr-2 h-4 w-4" /> Select Location </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-lg"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <MapPin className="h-5 w-5" /> Select Location </DialogTitle> <DialogDescription> Search for an address or select from saved locations. </DialogDescription> </DialogHeader> <div className="space-y-4 py-4"> <div className="relative"> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input placeholder="Search address or place..." value={search} onChange={(e) => handleSearchChange(e.target.value)} onFocus={handleSearchFocus} className="pl-9" /> {showSuggestions && filteredSuggestions.length > 0 && ( <div className="absolute top-full left-0 right-0 z-10 mt-1 rounded-md border bg-popover shadow-md"> {filteredSuggestions.map((location) => ( <button key={location.id} onClick={() => handleSelectLocation(location)} className="w-full flex items-start gap-3 p-3 hover:bg-muted text-left" > <MapPin className="h-4 w-4 text-muted-foreground mt-0.5 shrink-0" /> <div> <p className="text-sm font-medium">{location.name}</p> <p className="text-xs text-muted-foreground">{location.address}</p> </div> </button> ))} </div> )} </div> <Button variant="outline" className="w-full" onClick={handleUseCurrentLocation} disabled={isLocating} > <Crosshair className={cn("mr-2 h-4 w-4", isLocating && "animate-pulse")} /> {isLocating ? "Detecting location..." : "Use current location"} </Button> <div className="relative h-32 rounded-lg border-2 border-dashed bg-muted/30 overflow-hidden" > <div className="absolute inset-0 flex items-center justify-center"> {selectedLocation ? ( <div className="text-center"> <MapPin className="h-8 w-8 text-primary mx-auto mb-1" /> <p className="text-xs font-medium">{selectedLocation.name}</p> </div> ) : ( <div className="text-center"> <Navigation className="h-8 w-8 text-muted-foreground mx-auto mb-1" /> <p className="text-xs text-muted-foreground">Map preview</p> </div> )} </div> <div className="absolute bottom-2 right-2"> <Badge variant="secondary" className="text-xs"> Interactive map </Badge> </div> </div> {selectedLocation && ( <div className="rounded-lg border p-3"> <div className="flex items-start justify-between gap-2"> <div className="flex-1 min-w-0"> <p className="font-medium text-sm">{selectedLocation.name}</p> <p className="text-xs text-muted-foreground truncate"> {selectedLocation.address} </p> </div> <Button variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={handleCopyCoordinates} > {copied ? ( <Check className="h-4 w-4 text-primary" /> ) : ( <Copy className="h-4 w-4" /> )} </Button> </div> <div className="flex items-center gap-2 mt-2"> <Badge variant="outline" className="text-xs font-mono"> {selectedLocation.lat.toFixed(4)}, {selectedLocation.lng.toFixed(4)} </Badge> </div> </div> )} <Separator /> <div className="space-y-3"> {savedLocations.length > 0 && ( <div className="space-y-2"> <Label className="text-xs text-muted-foreground flex items-center gap-1"> <Star className="h-3 w-3" /> Saved Places </Label> <div className="grid grid-cols-2 gap-2"> {savedLocations.map((location) => ( <button key={location.id} onClick={() => handleSelectLocation(location)} className={cn( "flex items-center gap-2 p-2 rounded-lg border text-left transition-colors hover:bg-muted", selectedLocation?.id === location.id && "border-primary bg-primary/5" )} > <MapPin className="h-4 w-4 text-muted-foreground shrink-0" /> <div className="min-w-0"> <p className="text-sm font-medium truncate">{location.name}</p> <p className="text-xs text-muted-foreground truncate"> {location.address.split(",")[0]} </p> </div> </button> ))} </div> </div> )} {recentLocations.length > 0 && ( <div className="space-y-2"> <Label className="text-xs text-muted-foreground flex items-center gap-1"> <Clock className="h-3 w-3" /> Recent </Label> <ScrollArea className="h-[80px]"> <div className="space-y-1"> {recentLocations.map((location) => ( <button key={location.id} onClick={() => handleSelectLocation(location)} className={cn( "w-full flex items-center gap-2 p-2 rounded-lg text-left transition-colors hover:bg-muted", selectedLocation?.id === location.id && "bg-primary/5" )} > <Clock className="h-3 w-3 text-muted-foreground shrink-0" /> <div className="min-w-0 flex-1"> <p className="text-sm truncate">{location.name}</p> </div> </button> ))} </div> </ScrollArea> </div> )} </div> </div> <DialogFooter className="gap-2 sm:gap-0"> <Button variant="outline" onClick={handleClose}> Cancel </Button> <Button onClick={handleConfirm} disabled={!selectedLocation}> <Check className="mr-2 h-4 w-4" /> Confirm Location </Button> </DialogFooter> </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-location-picker.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-location-picker.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-location-picker.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-location-picker.jsonRelated blocks you will also like
React Dialog Block Timezone Picker
Geographic selection
React Dialog Block Billing Address
Address input
React Dialog Block Emergency Contact
Location context
React Dialog Block Schedule Meeting
Meeting location
React Dialog Block Success Confirmation
Location confirmation
React Dialog Block Edit Profile Form
Profile location