About this snippet

A two column contact block with the form on one side and a live map on the other, plus an address and phone card sitting over the map.

The map is Leaflet with OpenStreetMap tiles, which needs no API key and no billing account. The marker uses a custom icon, and its anchor is set to the bottom center of the image so the point of the pin lands on the coordinates rather than the middle of the graphic.

Change the latitude and longitude in the setView call to move the map, and keep the OpenStreetMap attribution, since their tile usage policy requires it. The form is markup only with no submit handler, so point it at your own endpoint. Leaflet loads from a CDN and setup runs behind a short timeout to wait for it, which you can drop once the library is part of your own build.

Size
5.7 KB
Uses
HTML, CSS, JavaScript
Loads
cdnjs.cloudflare.com, tile.openstreetmap.org
Views
223
<!-- Leaflet CSS & JS included for the map -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div class="contact-section">
  <div class="contact-grid">
    
    <div class="contact-form-panel">
      <h2>Get in Touch</h2>
      <p>We would love to hear from you. Drop us a line and we will get back to you shortly.</p>
      
      <div class="form-group">
        <label>Full Name</label>
        <input type="text" placeholder="John Doe">
      </div>
      
      <div class="form-group">
        <label>Email Address</label>
        <input type="email" placeholder="john@example.com">
      </div>
      
      <div class="form-group">
        <label>Message</label>
        <textarea rows="4" placeholder="How can we help?"></textarea>
      </div>
      
      <button class="submit-btn">Send Message</button>
    </div>
    
    <div class="map-panel">
      <div id="map-custom"></div>
      
      <div class="contact-info-card">
        <div class="info-item">
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
          <span>123 Design Avenue, Paris</span>
        </div>
        <div class="info-item">
          <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
          <span>+33 1 23 45 67 89</span>
        </div>
      </div>
    </div>
    
  </div>
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  background: #f1f5f9;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 40px 20px;
}

.contact-section {
  width: 100%;
  max-width: 1100px;
  background: #ffffff;
  border-radius: 24px;
  box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.contact-form-panel {
  padding: 60px 50px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.contact-form-panel h2 {
  margin: 0 0 10px 0;
  font-size: 2.5rem;
  color: #0f172a;
  letter-spacing: -1px;
}

.contact-form-panel p {
  color: #64748b;
  font-size: 1.125rem;
  margin-bottom: 40px;
  line-height: 1.6;
}

.form-group {
  margin-bottom: 24px;
}

.form-group label {
  display: block;
  margin-bottom: 8px;
  font-weight: 600;
  color: #334155;
  font-size: 0.95rem;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 16px;
  background: #f8fafc;
  border: 1px solid transparent;
  border-radius: 12px;
  font-family: inherit;
  font-size: 1rem;
  color: #0f172a;
  transition: all 0.3s ease;
  resize: vertical;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  background: #ffffff;
  border-color: #3b82f6;
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
}

.submit-btn {
  background: #3b82f6;
  color: #ffffff;
  border: none;
  padding: 18px;
  border-radius: 12px;
  font-size: 1.125rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.3s ease, transform 0.2s ease;
  margin-top: 10px;
}

.submit-btn:hover {
  background: #2563eb;
  transform: translateY(-2px);
}

.map-panel {
  position: relative;
  min-height: 600px;
  background: #e2e8f0;
}

#map-custom {
  width: 100%;
  height: 100%;
  z-index: 1;
}

.contact-info-card {
  position: absolute;
  bottom: 40px;
  left: 40px;
  right: 40px;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(16px);
  padding: 24px;
  border-radius: 16px;
  box-shadow: 0 15px 30px -5px rgba(0, 0, 0, 0.2);
  z-index: 1000; /* Important to place above Leaflet elements */
  display: flex;
  flex-direction: column;
  gap: 16px;
  border: 1px solid rgba(255, 255, 255, 0.5);
}

.info-item {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #0f172a;
  font-weight: 600;
  font-size: 1.05rem;
}

.info-item svg {
  color: #3b82f6;
  flex-shrink: 0;
}

@media (max-width: 900px) {
  .contact-grid {
    grid-template-columns: 1fr;
  }
  .map-panel {
    height: 450px;
    min-height: auto;
  }
  .contact-form-panel {
    padding: 40px 24px;
  }
  .contact-info-card {
    bottom: 20px;
    left: 20px;
    right: 20px;
  }
}
// We use a small timeout to ensure the Leaflet CDN script is fully loaded before executing
setTimeout(function() {
  
  // Note: We use L.latLng() instead of standard arrays to avoid database text parser conflicts
  const mapCenter = L.latLng(48.8566, 2.3522);
  const map = L.map("map-custom").setView(mapCenter, 13);
  
  L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: "OpenStreetMap"
  }).addTo(map);
  
  // Note: We use L.point() instead of standard arrays for the same reason
  const storeIcon = L.icon({
    iconUrl: "https://placehold.co/50x50/3b82f6/ffffff?text=Store",
    iconSize: L.point(50, 50),
    iconAnchor: L.point(25, 50),
    popupAnchor: L.point(0, -50)
  });
  
  L.marker(mapCenter, {icon: storeIcon}).addTo(map)
    .bindPopup("<b>Main Store</b><br>Come visit us today!")
    .openPopup();
    
}, 500);
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save