Retour au catalogue

Contact Map Integrated

Section contact avec carte Google Maps integree en fond et overlay d'informations.

contactsimple Both Responsive a11y
minimalcorporaterestaurantbeautyplumbinguniversalstacked
Theme

How to build a contact section with an embedded map in React

A React contact section with an embedded map renders a Google Maps iframe inside a rounded container, then displays address, email and phone in a sticky info bar below the map. Framer Motion animates both blocks into view as the user scrolls.

  • Stack: React + Framer Motion + lucide-react + Tailwind v4, ~170 lines, no extra deps.
  • The iframe carries loading='lazy' and referrerPolicy='no-referrer-when-downgrade' for performance and privacy.
  • All colours come from CSS custom properties (--color-background, --color-accent, etc.), zero hardcoded values.
  • Accessible: the iframe has a title attribute; email and phone are real anchor tags (mailto:/tel:).
  • Responsive by default, the map switches from h-80 to h-96 at the lg breakpoint.

Contact Map Integrated pairs a full-width embedded map with a compact info bar that surfaces address, email and phone in one scannable row. The two blocks slide in on scroll via Framer Motion, the second block staggered 100 ms behind the first. It suits any brick-and-mortar brand that needs visitors to find them quickly, restaurants, studios, clinics.

Anatomy

The section has two motion.div blocks inside a max-w-6xl container. The first contains the badge, h2 and optional subtitle. The second is a rounded div with a 1 px border that wraps two children: the map area (an iframe or a fallback MapPin icon when no URL is provided) and the info bar below, which uses flex-wrap to arrange contact items and pushes the CTA link to the far right with ml-auto.

How it works

Both blocks use the same Framer Motion pattern: initial={{ opacity: 0, y: 20 }}, whileInView={{ opacity: 1, y: 0 }}, viewport={{ once: true }}. The cubic-bezier ease [0.16, 1, 0.3, 1] gives each block a fast rise that decelerates smoothly, sharper than the default easeOut and closer to a spring without needing useSpring. The second block adds delay: 0.1 to create a light stagger. The iframe handles its own map rendering; React only manages the container and the surrounding UI.

How to build it in React

  1. Create the section shell and header block

    Wrap everything in a section tag with a bg CSS token. Inside a max-w-6xl container, place a motion.div that slides up on scroll for the badge, h2 and subtitle. This block animates independently from the map so the heading has time to appear before the map follows.

    const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];
    
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6, ease }}
      viewport={{ once: true }}
    >
      {badge && <span className="uppercase tracking-widest text-xs">{badge}</span>}
      <h2>{title}</h2>
    </motion.div>
  2. Embed the map iframe with a fallback

    Render the iframe when a mapEmbedUrl prop is present; show a centered MapPin icon otherwise. Set loading='lazy' to defer the iframe until it is near the viewport. The iframe title attribute is required for accessibility, screen readers announce it as a landmark.

    {mapEmbedUrl ? (
      <iframe
        src={mapEmbedUrl}
        className="w-full h-full"
        style={{ border: 0 }}
        allowFullScreen
        loading="lazy"
        referrerPolicy="no-referrer-when-downgrade"
        title="Carte"
      />
    ) : (
      <div className="flex items-center justify-center h-full">
        <MapPin size={40} />
      </div>
    )}
  3. Build the contact info bar

    Below the map, add a flex-wrap row with the three contact items (address, email, phone) conditionally rendered. Each item pairs a lucide icon with its text or anchor. Push the directions CTA link to the right with ml-auto. Phone links use tel: with whitespace stripped; email links use mailto:.

    <a href={`tel:${phone.replace(/\s/g, "")}`} className="text-sm hover:opacity-70">
      {phone}
    </a>
    <a href={ctaUrl} target="_blank" rel="noopener noreferrer" className="ml-auto flex items-center gap-2">
      {ctaLabel} <ExternalLink size={14} />
    </a>
  4. Stagger the map block 100 ms

    Wrap the rounded map container in a second motion.div with the same initial/whileInView config but add delay: 0.1 to the transition. This creates a two-step reveal, header settles first, map follows, without any complex orchestration.

    <motion.div
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6, ease, delay: 0.1 }}
      viewport={{ once: true }}
      className="rounded-xl overflow-hidden"
    >
      {/* map + info bar */}
    </motion.div>

When to use it

Use this section when physical location is a key conversion signal, restaurants, beauty salons, clinics, co-working spaces, any service that depends on foot traffic. Place it at the bottom of the page, after testimonials or FAQ, where visitors who have decided to visit go to confirm the address. Skip it for fully remote services, SaaS products or pages where a contact form alone is sufficient.

Used by

  • Airbnb, Listing pages combine an embedded map with contact-style info bars showing host details and location.
  • OpenTable, Restaurant profile pages display an interactive map alongside address, phone and directions CTA in a stacked layout.
  • Doctolib, Practitioner pages show clinic location on a map next to an info strip with address and phone number.
  • WeWork, Location pages embed maps above a bar summarising contact details and directions for each co-working space.

FAQ

How do I get a Google Maps embed URL?

Open Google Maps, search for the location, click Share, choose Embed a map, copy the src value from the iframe snippet. Paste it into the mapEmbedUrl prop. Note that the Maps Embed API is free for this use case but requires a browser API key for high-volume sites.

Can I replace Google Maps with OpenStreetMap?

Yes, swap the iframe src for an OpenStreetMap embed URL (e.g. https://www.openstreetmap.org/export/embed.html?bbox=...). The component only renders what the src points to; no Google-specific code lives in the React layer.

Does the map affect page performance?

The iframe uses loading='lazy' so the browser defers fetching the map until the section is near the viewport. For pages with a strict Core Web Vitals budget, consider replacing the iframe with a static map image that links to Google Maps on click, this avoids third-party script load entirely.

How do I hide the map on mobile and show only the contact details?

Add a Tailwind class like 'hidden sm:block' to the iframe wrapper div. The info bar below is layout-agnostic and renders fine on its own at any viewport width.

"use client";

import { motion } from "framer-motion";
import { MapPin, Mail, Phone, ExternalLink } from "lucide-react";

interface ContactMapIntegratedProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  mapEmbedUrl?: string;
  address?: string;
  email?: string;
  phone?: string;
  ctaLabel?: string;
  ctaUrl?: string;
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

export default function ContactMapIntegrated({
  badge = "Nous trouver",
  title = "Venez nous rendre visite",

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Contact Section with Embedded Google Map, Tutorial