A contact-page map has one modest job: help a visitor recognize the place and get there. The awkward part is that an example which looked fine a few years ago may now use a deprecated marker, an unrestricted key, or a map container with zero height. Here is a current implementation that treats those details as part of the feature, not cleanup work.

What you need before writing code

  • A Google Cloud project with billing configured according to current Google Maps Platform terms.

  • Maps JavaScript API enabled for that project.

  • A browser API key restricted to authorized website referrers and to Maps JavaScript API.

  • A map ID for AdvancedMarkerElement; Google’s DEMO_MAP_ID is suitable for local experimentation, not a production configuration.

  • Verified latitude and longitude for the entrance visitors should use—not merely the center of a postal area.

The finished page has three layers

  • Semantic HTML provides the business name, postal address, directions link, and map region.

  • CSS reserves responsive space so the map can render without causing layout shift.

  • JavaScript loads the maps and marker libraries, creates the map, and places an advanced marker.

  • The address remains useful if JavaScript, the API, consent, or a network request fails.

1. Build an accessible contact section

contact.htmlhtml
<section class="contact-location" aria-labelledby="visit-us-title">
  <div class="contact-location__details">
    <h2 id="visit-us-title">Visit our Pune office</h2>
    <address>
      123 Example Road<br>
      Pune, Maharashtra 411001<br>
      India
    </address>
    <a href="https://www.google.com/maps/dir/?api=1&amp;destination=18.5204%2C73.8567"
       rel="noopener" target="_blank">
      Get directions in Google Maps
    </a>
  </div>
 
  <div id="contact-map"
       class="contact-map"
       role="region"
       aria-label="Map showing our Pune office"></div>
  <p id="map-status" class="map-status" aria-live="polite"></p>
</section>
 
<script src="/contact-map.js"></script>
<script async
  src="https://maps.googleapis.com/maps/api/js?key=YOUR_BROWSER_API_KEY&amp;loading=async&amp;callback=initMap&amp;v=weekly&amp;libraries=marker">
</script>

The address is content; the map is enhancement

  • <address> identifies contact information rather than serving as a generic italic-text element.

  • The directions URL works independently of the JavaScript map and percent-encodes its coordinates.

  • aria-label gives the map region a purpose; it should not repeat a long street address already visible nearby.

  • The live status region can announce a loading failure without trapping keyboard focus.

  • async and loading=async avoid blocking HTML parsing while the callback initializes after the API is ready.

2. Reserve responsive map space

contact-map.csscss
.contact-location {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: minmax(0, 1fr);
}
 
.contact-map {
  width: 100%;
  min-height: 22rem;
  border-radius: 0.75rem;
  background: #e8eaed;
  overflow: hidden;
}
 
.map-status:not(:empty) {
  padding: 0.75rem;
  border-left: 0.25rem solid #b45309;
}
 
@media (min-width: 56rem) {
  .contact-location {
    grid-template-columns: minmax(16rem, 0.7fr) minmax(0, 1.3fr);
    align-items: stretch;
  }
}

The height rule prevents the classic blank rectangle

  • A Google map needs a container with a computed height; width alone is insufficient.

  • minmax(0, ...) lets grid children shrink instead of forcing horizontal overflow.

  • The neutral background acts as a stable placeholder while scripts load.

  • A minimum height keeps touch targets and labels usable on mobile without fixing every viewport to one size.

  • Reserve space before loading to reduce cumulative layout shift.

3. Initialize the map and advanced marker

contact-map.jsjavascript
const OFFICE = { lat: 18.5204, lng: 73.8567 };
 
window.initMap = async function initMap() {
  const mapElement = document.getElementById("contact-map");
  const statusElement = document.getElementById("map-status");
 
  if (!mapElement) return;
 
  try {
    const { Map } = await google.maps.importLibrary("maps");
    const { AdvancedMarkerElement } =
      await google.maps.importLibrary("marker");
 
    const map = new Map(mapElement, {
      center: OFFICE,
      zoom: 16,
      mapId: "YOUR_PRODUCTION_MAP_ID",
      streetViewControl: false,
      mapTypeControl: false,
      fullscreenControl: true,
    });
 
    new AdvancedMarkerElement({
      map,
      position: OFFICE,
      title: "Example Company — Pune office",
    });
  } catch (error) {
    console.error("Unable to initialize contact map", error);
    if (statusElement) {
      statusElement.textContent =
        "The interactive map is unavailable. Use the directions link above.";
    }
  }
};

One coordinate object prevents tiny mismatches

  • The map center and marker share the same latitude/longitude object.

  • importLibrary("marker") loads the library that owns AdvancedMarkerElement.

  • Advanced markers require a map ID. Use an environment-specific production map ID rather than shipping the demo identifier.

  • The marker title supplies a concise accessible name and hover text.

  • The catch path preserves a useful fallback while logging technical detail for developers.

Restrict the browser key before deployment

  1. In Google Cloud Console, open the credential used only by this web application.

  2. Set the application restriction to Websites.

  3. Authorize exact HTTPS origins/referrer patterns needed by production and staging; add the explicit localhost origin and port only when local development needs the same key.

  4. Set API restrictions so the key can call Maps JavaScript API and only other services this page genuinely uses.

  5. Save, allow configuration to propagate, test each authorized environment, and inspect usage metrics for rejected or unexpected traffic.

Browser keys are identifiers with guardrails, not secrets

  • Do not reuse a server-side web-service key in browser JavaScript.

  • Server-side keys belong outside source control and normally need IP/API restrictions or supported OAuth flows.

  • Use separate keys per application/environment where practical so an incident has a smaller blast radius.

  • Never commit a real key into a public tutorial, repository, screenshot, or support log.

  • Set budget alerts and usage monitoring; restrictions reduce abuse but operational visibility still matters.

Get coordinates without silently geocoding every visit

  • For a fixed office, geocode and verify the address during content setup, then store the approved coordinates.

  • Confirm the pin at the customer entrance, parking gate, loading dock, or reception as appropriate.

  • Do not call a geocoding service on every page view for an address that rarely changes.

  • Keep displayed address, structured organization/location data, directions destination, and marker coordinates synchronized.

  • Treat private residential or sensitive facility coordinates according to the organization’s privacy and safety policy.

Optional: open a small information window

contact-map.js (inside initMap)javascript
const { InfoWindow } = await google.maps.importLibrary("maps");
 
const infoWindow = new InfoWindow({
  content: "<strong>Example Company</strong><br>Reception entrance",
  ariaLabel: "Example Company reception entrance",
});
 
marker.addListener("click", () => {
  infoWindow.open({ map, anchor: marker });
});

Keep pop-up content small and trusted

  • Store the marker in a marker variable before attaching the listener.

  • InfoWindow is useful for a short label or action, not an entire contact form.

  • Never interpolate unsanitized user-controlled HTML into content.

  • ariaLabel describes the window to assistive technology.

  • The visible page should still carry the canonical address and contact information.

Content Security Policy considerations

  • A strict CSP must permit the current Google Maps script, frame, image, style, font, and connection origins actually used by your integration.

  • Prefer nonce/hash-based first-party script policy instead of broadly allowing inline scripts.

  • Start from Google’s current CSP documentation and inspect browser violation reports; Maps resource hosts can evolve.

  • Do not weaken default-src globally just to eliminate one console error.

  • Re-test CSP whenever adding Places, Street View, analytics, consent tooling, or custom marker content.

  • Loading an interactive third-party map initiates external requests; review applicable privacy disclosures and consent requirements for the audience and jurisdiction.

  • A click-to-load placeholder can defer third-party requests until the visitor asks for the map.

  • Lazy-load below-the-fold maps after preserving layout space, but keep the address/directions link immediately available.

  • Do not place a heavyweight interactive map on every page when only the contact page needs it.

  • Measure Core Web Vitals and real-user behavior rather than assuming async loading makes the integration free.

When an iframe is the better answer

  • Choose Maps Embed API for a straightforward place, view, directions, search, or Street View embed with minimal application logic.

  • Choose Maps JavaScript API for custom programmatic markers, events, overlays, data-driven behavior, or coordinated UI state.

  • A plain external directions link is the most resilient and privacy-light option when an inline map adds little value.

  • Use a separate restricted key for Maps Embed API according to Google’s security guidance.

  • Product need—not visual novelty—should decide which integration ships.

Troubleshooting a blank or rejected map

  • The container is blank with no error: inspect computed height and ensure #contact-map exists before initialization.

  • `initMap` is not a function: expose the callback on window before the API finishes loading and check script order/path.

  • `RefererNotAllowedMapError`: add the exact HTTPS hostname/referrer pattern and verify staging/www variants.

  • `ApiNotActivatedMapError`: enable Maps JavaScript API in the key’s project.

  • Advanced marker does not appear: load the marker library, provide a valid position, and configure a map ID.

  • Billing or quota error: verify the project billing state, quotas, restrictions, and usage dashboard.

  • Map centers incorrectly: check latitude/longitude order, sign, precision, and the chosen entrance.

  • Works locally but not in production: compare key restrictions, CSP, domain scheme/hostname, deployment variables, and browser console/network errors.

Production verification

  • Test keyboard navigation, zoom controls, directions link, high contrast, mobile layout, and screen-reader context.

  • Test JavaScript blocked, API blocked, slow network, and consent-declined states; the address must remain usable.

  • Verify the key accepts only intended sites and intended APIs.

  • Check the marker against the real entrance and test the directions destination from a mobile device.

  • Inspect console errors, CSP reports, usage metrics, billing alerts, and Core Web Vitals after deployment.

  • Document ownership for key rotation, office-coordinate updates, map ID/style changes, and incident response.

Official references