Modern Google Places Autocomplete for Next.js – shadcn-google-maps

Build address search in Next.js with shadcn-google-maps: Places API (New) autocomplete with typed results, keyboard nav, and country filtering.

shadcn-google-maps is a modern PlacesAutocomplete component for shadcn/ui that adds Google Places address autocomplete to Next.js projects using the new Places API.

Embed it into any form, pass an onPlaceSelect callback, and get back a typed SelectedPlace object with the formatted address, latitude, longitude, and place ID.

No global google.maps.* types leak into your application code. No fighting with legacy widget styles.

Features

  • Adds address autocomplete to shadcn/ui forms.
  • Matches existing shadcn/ui input styles.
  • Returns typed address data for form state and backend payloads.
  • Supports keyboard selection for suggestion lists.
  • Debounces user input before remote lookups.
  • Restricts suggestions to a selected country.
  • Shows Google attribution for address suggestions.
  • Supports controlled and uncontrolled input patterns.
  • Accepts a custom API key through props.
  • Installs through the shadcn CLI or manual file copy.

Use Cases

  • Building a checkout form with address autocomplete.
  • Adding a location search to a store finder.
  • Capturing a shipping address during user onboarding.
  • Creating a geolocation picker for delivery radius selection.

How to Use It

Installation

Enable the required Google services before adding the component to your app.

  1. Create or select a project in Google Cloud Console.
  2. Enable Maps JavaScript API.
  3. Enable Places API (New).
  4. Create an API key under APIs & Services.
  5. Restrict the key by HTTP referrer.
  6. Restrict the key to Maps JavaScript API and Places API (New).

Add the key to .env.local:

NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your_key_here

The NEXT_PUBLIC_ prefix exposes the key to browser code. Referrer restrictions protect the key from use on unrelated domains.

Install the component through the shadcn CLI:

bunx shadcn@latest add https://shadcn-google-maps.vercel.app/r/places-autocomplete.json

Install the required dependency and shadcn/ui components:

bun add lucide-react
bun add -d @types/google.maps
bunx shadcn@latest add input button

You can also copy the component files manually into your project, usually under components/ui/ and hooks/.

Basic Usage

Add the component inside a client component. The onPlaceSelect callback receives the selected address data.

"use client"
import { PlacesAutocomplete } from "@/components/ui/places-autocomplete"
export function AddressField() {
  return (
    <PlacesAutocomplete
      placeholder="Search for an address"
      countryCode="us"
      onPlaceSelect={(place) => {
        console.log(place.address)
        console.log(place.lat)
        console.log(place.lng)
        console.log(place.placeId)
      }}
    />
  )
}

The selected value follows this shape:

type SelectedPlace = {
  address: string
  lat: number | null
  lng: number | null
  placeId: string | null
}

Available Component Props

PropTypeDefaultDescription
onPlaceSelect(place: SelectedPlace) => voidRequiredRuns after the user selects a suggestion.
valuestringSets the controlled input value.
defaultValuestring""Sets the initial uncontrolled value.
onValueChange(value: string) => voidRuns when the input text changes.
apiKeystringEnv keyOverrides NEXT_PUBLIC_GOOGLE_MAPS_API_KEY.
countryCodestring | nullLimits suggestions to an ISO region code such as us.
debounceMsnumber300Sets the delay before fetching suggestions.
placeholderstring"Start typing an address"Sets the input placeholder text.
disabledbooleanfalseDisables the input.
classNamestringAdds classes to the wrapper.
inputClassNamestringAdds classes to the input element.
showPoweredByGooglebooleantrueShows Google attribution for suggestions.

API Methods

PlacesAutocomplete does not expose imperative public methods. Use React props and callbacks to control the field, update state, and process selected place data.

<PlacesAutocomplete
  value={address}
  onValueChange={setAddress}
  onPlaceSelect={(place) => {
    setAddress(place.address)
  }}
/>

Events

The component exposes callback props instead of DOM style custom events.

<PlacesAutocomplete
  onValueChange={(value) => {
    // Runs whenever the user edits the input text.
    setAddress(value)
  }}
  onPlaceSelect={(place) => {
    // Runs after the user selects a suggestion.
    setSelectedPlace(place)
  }}
/>

Real-world Examples

Example 1: Controlled Address Field

Use a controlled value when the address must stay in sync with a parent form component.

"use client"
import { useState } from "react"
import { PlacesAutocomplete } from "@/components/ui/places-autocomplete"
export function ControlledAddressField() {
  const [address, setAddress] = useState("")
  return (
    <div className="space-y-2">
      <label className="text-sm font-medium">Address</label>
      <PlacesAutocomplete
        value={address}
        onValueChange={setAddress}
        placeholder="Start typing your address"
        countryCode="us"
        onPlaceSelect={(place) => {
          setAddress(place.address)
        }}
      />
      <p className="text-sm text-muted-foreground">
        Current value: {address || "No address selected"}
      </p>
    </div>
  )
}

Example 2: Checkout Form Address Capture

Use SelectedPlace data when a checkout form needs both a readable address and location coordinates.

"use client"
import { useState } from "react"
import { PlacesAutocomplete } from "@/components/ui/places-autocomplete"
type CheckoutPlace = {
  address: string
  lat: number | null
  lng: number | null
  placeId: string | null
}
export function CheckoutAddressForm() {
  const [shippingAddress, setShippingAddress] = useState("")
  const [place, setPlace] = useState<CheckoutPlace | null>(null)
  function submitAddress() {
    const payload = {
      shippingAddress,
      coordinates: place
        ? {
            lat: place.lat,
            lng: place.lng,
          }
        : null,
      googlePlaceId: place?.placeId ?? null,
    }
    console.log("Checkout payload", payload)
  }
  return (
    <div className="space-y-4">
      <PlacesAutocomplete
        value={shippingAddress}
        onValueChange={setShippingAddress}
        placeholder="Enter your shipping address"
        countryCode="us"
        debounceMs={400}
        onPlaceSelect={(selectedPlace) => {
          setPlace(selectedPlace)
          setShippingAddress(selectedPlace.address)
        }}
      />
      <button
        type="button"
        className="rounded-md bg-primary px-4 py-2 text-primary-foreground"
        onClick={submitAddress}
      >
        Save shipping address
      </button>
    </div>
  )
}

Example 3: Booking Form with Country Restriction

Set countryCode when your product only supports bookings in one region.

"use client"
import { useState } from "react"
import { PlacesAutocomplete } from "@/components/ui/places-autocomplete"
export function VenueBookingLocation() {
  const [venue, setVenue] = useState("")
  const [venuePlaceId, setVenuePlaceId] = useState<string | null>(null)
  return (
    <form className="space-y-4">
      <div className="space-y-2">
        <label className="text-sm font-medium">Venue</label>
        <PlacesAutocomplete
          value={venue}
          onValueChange={setVenue}
          placeholder="Search for a venue"
          countryCode="us"
          inputClassName="h-11"
          onPlaceSelect={(place) => {
            setVenue(place.address)
            setVenuePlaceId(place.placeId)
          }}
        />
      </div>
      <input type="hidden" name="venuePlaceId" value={venuePlaceId ?? ""} />
      <button
        type="submit"
        className="rounded-md border px-4 py-2"
      >
        Continue
      </button>
    </form>
  )
}

Example 4: Custom API Key per Component

Pass apiKey directly when a page needs a specific key instead of the shared environment variable.

"use client"
import { PlacesAutocomplete } from "@/components/ui/places-autocomplete"
export function PartnerLocationSearch() {
  return (
    <PlacesAutocomplete
      apiKey={process.env.NEXT_PUBLIC_PARTNER_GOOGLE_MAPS_API_KEY}
      placeholder="Search partner locations"
      countryCode="ca"
      showPoweredByGoogle={true}
      onPlaceSelect={(place) => {
        console.log("Partner place", place)
      }}
    />
  )
}

Alternatives and Related Resources

FAQs

Q: Which Google APIs should I enable?
A: Enable Maps JavaScript API and Places API (New).

Q: Can I use it as a controlled form field?
A: Yes. Pass value and onValueChange to control the input from React state. Use onPlaceSelect to store the final selected address data.

Q: What data do I get after a user selects an address?
A: The callback returns a SelectedPlace object with address, lat, lng, and placeId. Latitude and longitude can be null when Google does not return coordinates.

Q: Can I limit suggestions to one country?
A: Yes. Pass an ISO region code to countryCode, such as us for the United States or ca for Canada.

Q: Does it work in server components?
A: Use the autocomplete field inside a client component. The component needs browser APIs, user input events, and Google Maps JavaScript loading.

gurbaaz27

gurbaaz27

Leave a Reply

Your email address will not be published. Required fields are marked *