Skip to content
Documentation menu
docs / examples / click-popup
Example

Click a feature for a popup

Click one of your points and get a popup built from its properties, with a pointer cursor on hover so people know it is clickable. The text goes in with setText, so a property holding markup is shown as text, never run.

ts
import { createMap } from "@mapmap/maps";
import * as maplibregl from "maplibre-gl";

const map = createMap({
  container: "map",
  apiKey: "snk_...",
  attributionPosition: "bottom-left",
  center: [-1.6, 53.2],
  zoom: 6,
});
await map.whenReady();

map.map.addSource("sites", {
  type: "geojson",
  data: {
    type: "FeatureCollection",
    features: [
      { type: "Feature", properties: { name: "London depot", open: "06:00 to 22:00" },
        geometry: { type: "Point", coordinates: [-0.1278, 51.5074] } },
      { type: "Feature", properties: { name: "Birmingham yard", open: "24 hours" },
        geometry: { type: "Point", coordinates: [-1.8904, 52.4862] } },
      { type: "Feature", properties: { name: "Leeds cross-dock", open: "05:00 to 21:00" },
        geometry: { type: "Point", coordinates: [-1.5491, 53.8008] } },
    ],
  },
});

// Slots arrive in the SDK 0.17: map.slot("middle") replaces "road-labels".
map.map.addLayer({
  id: "sites",
  type: "circle",
  source: "sites",
  paint: {
    "circle-radius": 8,
    "circle-color": "#1a6bff",
    "circle-stroke-color": "#ffffff",
    "circle-stroke-width": 2,
  },
}, "road-labels");

const popup = new maplibregl.Popup({ closeButton: true, offset: 12 });

map.map.on("click", "sites", (e) => {
  const feature = e.features?.[0];
  if (!feature || feature.geometry.type !== "Point") return;
  const { name, open } = feature.properties;
  popup
    .setLngLat(feature.geometry.coordinates as [number, number])
    .setText(name + ", open " + open)   // text, never HTML from your data
    .addTo(map.map);
});

map.map.on("mouseenter", "sites", () => {
  map.map.getCanvas().style.cursor = "pointer";
});
map.map.on("mouseleave", "sites", () => {
  map.map.getCanvas().style.cursor = "";
});

Worth knowing

  • Use the point's own coordinates rather than e.lngLat, so the popup sits on the feature and not wherever inside the circle the click landed.
  • For richer content build an element and pass it to setDOMContent. Reach for setHTML only with markup you wrote yourself, never with values from your data.
  • For points with clustering, popups and nearest-place answers already wired, PlacesLayer does all of this in one call.