Custom Markers and Popups โ€” Branded Map Icons

Why Custom Markers Matter

Default Leaflet markers are functional but generic. Custom markers make your map instantly recognizable, convey meaning (campsite vs. trailhead vs. viewpoint), and provide a better user experience. Rich popups turn markers into information cards.

Why this matters for your career:

  • Custom markers differentiate your map from generic examples
  • Visual hierarchy helps users understand the map at a glance
  • Rich popups reduce the need for a separate info panel
  • Custom icons are a common client requirement for branded applications

Custom Marker Icons

Using L.divIcon (No Image Files Needed)

// Simple colored circle marker
const campsiteIcon = L.divIcon({
  className: 'custom-marker',
  html: '<div class="marker-icon campsite"></div>',
  iconSize: [30, 30],
  iconAnchor: [15, 30],  // bottom-center
  popupAnchor: [0, -30]   // above the icon
});

const trailheadIcon = L.divIcon({
  className: 'custom-marker',
  html: '<div class="marker-icon trailhead">๐Ÿฅพ</div>',
  iconSize: [30, 30],
  iconAnchor: [15, 30],
  popupAnchor: [0, -30]
});

const viewpointIcon = L.divIcon({
  className: 'custom-marker',
  html: '<div class="marker-icon viewpoint">๐Ÿ“ท</div>',
  iconSize: [30, 30],
  iconAnchor: [15, 30],
  popupAnchor: [0, -30]
});

CSS for Custom Icons

.custom-marker {
  background: none !important;
  border: none !important;
}

.marker-icon {
  width: 30px;
  height: 30px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 16px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}

.marker-icon.campsite {
  background: linear-gradient(135deg, #22c55e, #16a34a);
}

.marker-icon.trailhead {
  background: linear-gradient(135deg, #3b82f6, #2563eb);
}

.marker-icon.viewpoint {
  background: linear-gradient(135deg, #f59e0b, #d97706);
}

/* Fix the rotation so icon content faces upright */
.marker-icon span,
.marker-icon::before {
  transform: rotate(45deg);
}

Using Image Icons

const campsiteIcon = L.icon({
  iconUrl: '/icons/campsite.svg',
  iconSize: [32, 32],
  iconAnchor: [16, 32],
  popupAnchor: [0, -32],
  shadowUrl: '/icons/marker-shadow.png',
  shadowSize: [41, 41],
  shadowAnchor: [13, 41]
});

// Create themed markers
const marker = L.marker([25.033, 121.565], { icon: campsiteIcon })
  .bindPopup('<b>Sunset Ridge Campground</b>')
  .addTo(map);

Marker Categories

| Type | Icon Color | Emoji | Use Case | |------|-----------|-------|----------| | Campsite | Green | โ›บ | Camping spots | | Trailhead | Blue | ๐Ÿฅพ | Hiking trail starts | | Viewpoint | Orange | ๐Ÿ“ท | Scenic viewpoints | | Parking | Gray | ๐Ÿ…ฟ๏ธ | Parking areas | | Water Source | Cyan | ๐Ÿ’ง | Water refill stations | | Toilet | Purple | ๐Ÿšป | Restroom facilities | | Emergency | Red | ๐Ÿ†˜ | Emergency contact points |

Rich Popups

Basic Popup

marker.bindPopup(`
  <h3>Sunset Ridge Campground</h3>
  <p>Beautiful mountain ridge with panoramic views. Suitable for tents and small RVs.</p>
`);

Rich Popup with Card Layout

marker.bindPopup(`
  <div class="popup-card">
    <img src="/images/sunset-ridge.jpg" alt="Sunset Ridge" class="popup-image" />
    <div class="popup-content">
      <h3>Sunset Ridge Campground</h3>
      <div class="popup-meta">
        <span class="badge">โ›บ Campsite</span>
        <span class="rating">โ˜…โ˜…โ˜…โ˜…โ˜†</span>
      </div>
      <p>Beautiful mountain ridge campsite with panoramic sunrise views. Suitable for tents and small RVs.</p>
      <div class="popup-details">
        <div><strong>Elevation:</strong> 1,200m</div>
        <div><strong>Price:</strong> NT$800/night</div>
        <div><strong>Amenities:</strong> Water, Toilet, Fire Pit</div>
      </div>
      <div class="popup-actions">
        <a href="/campsites/sunset-ridge" class="btn">View Details</a>
        <button onclick="getDirections(25.033, 121.565)" class="btn btn-secondary">Directions</button>
      </div>
    </div>
  </div>
`);

Popup CSS

.popup-card {
  width: 280px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

.popup-image {
  width: 100%;
  height: 140px;
  object-fit: cover;
  border-radius: 8px 8px 0 0;
}

.popup-content {
  padding: 12px;
}

.popup-content h3 {
  margin: 0 0 4px;
  font-size: 16px;
  color: #1a1a2e;
}

.popup-meta {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
}

.badge {
  background: #dcfce7;
  color: #166534;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.rating {
  color: #f59e0b;
  font-size: 14px;
}

.popup-details {
  font-size: 13px;
  color: #4b5563;
  margin-bottom: 12px;
}

.popup-details div {
  margin-bottom: 2px;
}

.popup-actions {
  display: flex;
  gap: 8px;
}

.popup-actions .btn {
  flex: 1;
  text-align: center;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 13px;
  text-decoration: none;
  cursor: pointer;
  border: none;
}

.btn {
  background: #22c55e;
  color: white;
}

.btn-secondary {
  background: #e5e7eb;
  color: #374151;
}

/* Mobile popup */
@media (max-width: 480px) {
  .popup-card {
    width: 220px;
  }
  .popup-image {
    height: 100px;
  }
  .popup-content h3 {
    font-size: 14px;
  }
}

Handling Popup Events

marker.on('click', function(e) {
  console.log('Marker clicked:', e.target.getLatLng());
  // Track analytics
  gtag('event', 'marker_click', {
    campsite_name: 'Sunset Ridge',
    campsite_id: 'abc-123'
  });
});

marker.on('popupopen', function() {
  console.log('Popup opened');
  // Pause map interaction while popup is open
  map.dragging.disable();
});

marker.on('popupclose', function() {
  console.log('Popup closed');
  map.dragging.enable();
});

Summary

Custom markers and rich popups transform a generic map into a branded, informative, and user-friendly application. Use L.divIcon for pure CSS markers or L.icon for image-based markers. Build rich popups with images, metadata, ratings, and action buttons.

Key takeaways:

  • L.divIcon creates CSS-only markers (no image files needed)
  • L.icon loads custom image icons
  • iconAnchor sets the point that aligns with the map coordinate
  • popupAnchor controls where the popup appears relative to the icon
  • Rich popups include images, badges, ratings, and action buttons
  • Style popup content with CSS for a card-like appearance
  • Use different marker shapes/colors for different POI types
  • Handle popup events for analytics and interaction control
  • Make popups responsive for mobile devices
  • Emoji markers are a quick way to add visual categorization

What's Next: Marker Clustering

The next chapter covers marker clustering โ€” handling hundreds of campsite markers with efficient clustering for a clean, performant map.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!