Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Address Autocomplete
Address input dialog with autocomplete suggestions, manual entry fallback, and address validation
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Enter addresses quickly with smart autocomplete. This React address autocomplete dialog provides search-as-you-type suggestions, address component breakdown for street, city, state, and zip, manual entry fallback option, and selected address confirmation display. Built with shadcn/ui Dialog, Input, Command, Button, and Label components using Tailwind CSS, users enter addresses accurately and efficiently. Start typing, select suggestion, confirm address—perfect for checkout flows, shipping forms, user profiles, or any Next.js application requiring accurate address collection with minimal user friction.
"use client";import { useState, useCallback } from "react";import { MapPin, Search, Loader2, Check, Edit2, Home, Building2,} from "lucide-react";import { Button } from "@/components/ui/button";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 { Command, CommandEmpty, CommandGroup, CommandItem, CommandList,} from "@/components/ui/command";import { cn } from "@/lib/utils";type AddressSuggestion = { id: string; description: string; mainText: string; secondaryText: string;};type AddressComponents = { street: string; apt: string; city: string; state: string; zip: string; country: string;};const mockSuggestions: AddressSuggestion[] = [ { id: "1", description: "123 Main Street, San Francisco, CA 94102, USA", mainText: "123 Main Street", secondaryText: "San Francisco, CA 94102, USA", }, { id: "2", description: "456 Market Street, San Francisco, CA 94103, USA", mainText: "456 Market Street", secondaryText: "San Francisco, CA 94103, USA", }, { id: "3", description: "789 Mission Street, San Francisco, CA 94105, USA", mainText: "789 Mission Street", secondaryText: "San Francisco, CA 94105, USA", },];export const title = "React Dialog Block Address Autocomplete";export default function DialogAddressAutocomplete() { const [open, setOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [isSearching, setIsSearching] = useState(false); const [suggestions, setSuggestions] = useState<AddressSuggestion[]>([]); const [showManualEntry, setShowManualEntry] = useState(false); const [selectedAddress, setSelectedAddress] = useState<AddressComponents | null>(null); const [isSubmitting, setIsSubmitting] = useState(false); const [manualAddress, setManualAddress] = useState<AddressComponents>({ street: "", apt: "", city: "", state: "", zip: "", country: "United States", }); const handleSearch = useCallback(async (query: string) => { setSearchQuery(query); if (query.length < 3) { setSuggestions([]); return; } setIsSearching(true); // Simulate API call await new Promise((resolve) => setTimeout(resolve, 500)); setSuggestions( mockSuggestions.filter((s) => s.description.toLowerCase().includes(query.toLowerCase()) ) ); setIsSearching(false); }, []); const handleSelectSuggestion = (suggestion: AddressSuggestion) => { // In real app: fetch full address details from API setSelectedAddress({ street: suggestion.mainText, apt: "", city: "San Francisco", state: "CA", zip: "94102", country: "United States", }); setSearchQuery(""); setSuggestions([]); }; const handleManualSubmit = () => { setSelectedAddress(manualAddress); setShowManualEntry(false); }; const handleSave = async () => { if (!selectedAddress) return; setIsSubmitting(true); await new Promise((resolve) => setTimeout(resolve, 1000)); setIsSubmitting(false); setOpen(false); }; const handleClose = () => { setOpen(false); setTimeout(() => { setSearchQuery(""); setSuggestions([]); setSelectedAddress(null); setShowManualEntry(false); setManualAddress({ street: "", apt: "", city: "", state: "", zip: "", country: "United States", }); }, 200); }; const formatAddress = (addr: AddressComponents) => { const lines: string[] = []; if (addr.apt) { lines.push(`${addr.street}, ${addr.apt}`); } else { lines.push(addr.street); } lines.push(`${addr.city}, ${addr.state} ${addr.zip}`); if (addr.country) { lines.push(addr.country); } return lines; }; 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" /> Add Address </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <MapPin className="h-5 w-5" /> {showManualEntry ? "Enter Address" : "Add Address"} </DialogTitle> <DialogDescription> {showManualEntry ? "Fill in the address details manually." : "Start typing to search for your address."} </DialogDescription> </DialogHeader> <div className="space-y-4 py-4 max-h-[60vh] overflow-y-auto"> {!showManualEntry && !selectedAddress && ( <> {/* Search Input */} <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 for an address..." value={searchQuery} onChange={(e) => handleSearch(e.target.value)} className="pl-9" /> {isSearching && ( <Loader2 className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" /> )} </div> {/* Suggestions */} {(suggestions.length > 0 || searchQuery.length >= 3) && ( <Command className="rounded-lg border"> <CommandList> <CommandEmpty> {isSearching ? "Searching..." : "No addresses found."} </CommandEmpty> <CommandGroup> {suggestions.map((suggestion) => ( <CommandItem key={suggestion.id} onSelect={() => handleSelectSuggestion(suggestion)} className="cursor-pointer" > <MapPin className="mr-2 h-4 w-4 text-muted-foreground" /> <div> <p className="text-sm font-medium"> {suggestion.mainText} </p> <p className="text-xs text-muted-foreground"> {suggestion.secondaryText} </p> </div> </CommandItem> ))} </CommandGroup> </CommandList> </Command> )} {/* Manual Entry Link */} <div className="text-center"> <Button variant="link" size="sm" onClick={() => setShowManualEntry(true)} > Enter address manually </Button> </div> </> )} {/* Manual Entry Form */} {showManualEntry && !selectedAddress && ( <div className="space-y-4"> <div className="space-y-2"> <Label htmlFor="street">Street Address</Label> <Input id="street" placeholder="123 Main Street" value={manualAddress.street} onChange={(e) => setManualAddress({ ...manualAddress, street: e.target.value }) } /> </div> <div className="space-y-2"> <Label htmlFor="apt">Apt, Suite, Unit (optional)</Label> <Input id="apt" placeholder="Apt 4B" value={manualAddress.apt} onChange={(e) => setManualAddress({ ...manualAddress, apt: e.target.value }) } /> </div> <div className="grid grid-cols-2 gap-4"> <div className="space-y-2"> <Label htmlFor="city">City</Label> <Input id="city" placeholder="San Francisco" value={manualAddress.city} onChange={(e) => setManualAddress({ ...manualAddress, city: e.target.value }) } /> </div> <div className="space-y-2"> <Label htmlFor="state">State</Label> <Input id="state" placeholder="CA" value={manualAddress.state} onChange={(e) => setManualAddress({ ...manualAddress, state: e.target.value }) } /> </div> </div> <div className="space-y-2"> <Label htmlFor="zip">ZIP Code</Label> <Input id="zip" placeholder="94102" value={manualAddress.zip} onChange={(e) => setManualAddress({ ...manualAddress, zip: e.target.value }) } /> </div> <div className="flex gap-2"> <Button variant="outline" className="flex-1" onClick={() => setShowManualEntry(false)} > Back to search </Button> <Button className="flex-1" onClick={handleManualSubmit} disabled={!manualAddress.street || !manualAddress.city} > Use this address </Button> </div> </div> )} {/* Selected Address Display */} {selectedAddress && ( <div className="rounded-lg border p-4"> <div className="flex items-start gap-3"> <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border"> <Home className="h-5 w-5" /> </div> <div className="flex-1 min-w-0 space-y-0.5"> {formatAddress(selectedAddress).map((line, i) => ( <p key={i} className={cn( "text-sm", i === 0 ? "font-medium" : "text-muted-foreground" )} > {line} </p> ))} </div> <Button variant="ghost" size="icon" className="shrink-0" onClick={() => setSelectedAddress(null)} > <Edit2 className="h-4 w-4" /> </Button> </div> </div> )} </div> <DialogFooter> <Button variant="outline" onClick={handleClose} disabled={isSubmitting}> Cancel </Button> <Button onClick={handleSave} disabled={!selectedAddress || isSubmitting} > {isSubmitting ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Saving... </> ) : ( <> <Check className="mr-2 h-4 w-4" /> Save Address </> )} </Button> </DialogFooter> </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-address-autocomplete.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-address-autocomplete.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-address-autocomplete.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-address-autocomplete.jsonRelated blocks you will also like
React Dialog Block Billing Address
Billing forms
React Dialog Block Location Picker
Location selection
React Dialog Block Edit Profile
Profile editing
React Dialog Block Add Card
Payment forms
React Dialog Block Register
User registration
React Dialog Block Success Confirmation
Address saved