mapcn-react-native
Core

Popups

MapPopup anchors to any coordinate via the renderer's native marker mechanism; MarkerPopup is a convenience wrapper for the per-marker case.

Renderer support: MapLibre ✓  ·  Mapbox ✓

npx mapcn-rn add popup
import { MapPopup, MarkerPopup } from "@/components/ui/mapcn";

Overview

MapPopup renders through MapMarkerAnchor -- the renderer's native marker/annotation positioning -- rather than manually re-projecting a coordinate to screen space on every viewport change. That means it never lags or drifts during pan/zoom, and it can anchor to any coordinate: a GeoJSON feature, a cluster, a polygon centroid, or an arbitrary map press, not just a marker.

MarkerPopup is a convenience wrapper for the common "a popup for this marker" case. It reads the enclosing MapMarker's coordinate from context when you don't pass one explicitly. Render it as a sibling of the MapMarker it belongs to (toggled by selection state) rather than literally nested inside <MapMarker> -- MapPopup creates its own independent marker-anchor, so nesting it inside another marker would anchor two markers to the same point.

Props

MapPopup

PropTypeDefaultDescription
coordinateCoordinateRequired. Where the popup anchors.
visiblebooleantrueRenders null when false.
onClose() => voidEnables the close button when provided.
closeButtonbooleantrueShows a close button (only if onClose is also set).
arrowbooleantrueShows a small pointer arrow beneath the popup.
maxWidthnumber260Max popup width in points.
classNamestringUniwind/NativeWind classes on the popup card.
styleStyleProp<ViewStyle>Merged with className.
childrenReactNodeRequired. Popup content.

MarkerPopup

Same as MapPopup except coordinate is optional (falls back to the enclosing marker) and it adds:

PropTypeDefaultDescription
titlestringRendered as a bold header above children.

MarkerPopup throws if it has no coordinate prop and isn't rendered where a MapMarker's context is available.

Example

import { useState } from "react";
import { Map } from "@/components/ui/mapcn";
import { MapMarker } from "@/components/ui/mapcn";
import { MapPopup, MarkerPopup } from "@/components/ui/mapcn";

export function PopupsExample() {
  const [selected, setSelected] = useState<string | null>(null);

  return (
    <Map defaultViewport={{ center: [-122.4194, 37.7749], zoom: 12 }} className="flex-1">
      <MapMarker coordinate={[-122.4194, 37.7749]} onPress={() => setSelected("sf")} />
      {selected === "sf" && (
        <MarkerPopup coordinate={[-122.4194, 37.7749]} title="San Francisco" onClose={() => setSelected(null)}>
          <Text>The city by the bay.</Text>
        </MarkerPopup>
      )}

      {/* Anchored to an arbitrary press, not a marker */}
      <MapPopup coordinate={[-122.42, 37.78]} onClose={() => {}}>
        <Text>Standalone popup</Text>
      </MapPopup>
    </Map>
  );
}

Live example

popup example screenshot

Renderer support

Neither renderer's native Callout is used -- MapPopup is intentionally built as its own overlay-anchored component so it can attach to non-marker coordinates (clusters, polygons, choropleth regions, arbitrary presses) uniformly, and so its styling and z-ordering are consistent across MapLibre and Mapbox.

Markers · GeoJSON · Clustering · Choropleths

On this page