Skip to main content
Annotations allow you to mark specific locations on the map with custom views, images, or React Native components. The SDK provides two components for annotations:
  • PointAnnotation - Renders children to a bitmap, best for static content
  • MarkerView - Interactive React Native views, best for dynamic/interactive markers

When to Use What

FeaturePointAnnotationMarkerViewShapeSource + SymbolLayer
PerformanceGoodModerateExcellent
InteractivityLimitedFullLimited
AnimationVia bitmap refreshNative React NativeExpression-based
Best forStatic markersInteractive UIThousands of markers
Max recommended~100~10010,000+
For best performance with many markers, use ShapeSource with SymbolLayer and static images.

PointAnnotation

PointAnnotation renders React Native views to a bitmap image that is displayed on the map. It’s optimized for static content.

Basic Usage

1

Import the component

import { MapView, PointAnnotation } from '@rnmapbox/maps';
2

Add annotations to your map

<MapView style={{ flex: 1 }}>
  <PointAnnotation
    id="marker1"
    coordinate={[-73.99155, 40.73581]}
    title="My Marker"
  >
    <View style={{
      width: 30,
      height: 30,
      borderRadius: 15,
      backgroundColor: 'red',
    }} />
  </PointAnnotation>
</MapView>

Props

PropTypeRequiredDescription
idstringYesUnique identifier for the annotation
coordinate[longitude, latitude]YesPosition on the map
titlestringNoAnnotation title (required for iOS callout)
snippetstringNoSubtitle text
selectedbooleanNoManually select/deselect annotation
draggablebooleanNoEnable dragging. Default: false
anchor{ x: number, y: number }NoAnchor point in [0, 1] space. Default: { x: 0.5, y: 0.5 }
childrenReactElementYesCustom view to render

Events

import { useRef } from 'react';
import { PointAnnotation, Callout } from '@rnmapbox/maps';

const InteractiveAnnotation = () => {
  const annotationRef = useRef<PointAnnotation>(null);

  return (
    <PointAnnotation
      ref={annotationRef}
      id="interactive-marker"
      coordinate={[-73.99155, 40.73581]}
      title="Draggable Marker"
      draggable={true}
      onSelected={(feature) => {
        console.log('Selected:', feature.geometry.coordinates);
      }}
      onDeselected={(feature) => {
        console.log('Deselected:', feature.id);
      }}
      onDragStart={(feature) => {
        console.log('Drag started at:', feature.geometry.coordinates);
      }}
      onDrag={(feature) => {
        console.log('Dragging:', feature.geometry.coordinates);
      }}
      onDragEnd={(feature) => {
        console.log('Dropped at:', feature.geometry.coordinates);
      }}
    >
      <View style={{
        width: 40,
        height: 40,
        borderRadius: 20,
        backgroundColor: 'blue',
        borderWidth: 2,
        borderColor: 'white',
      }} />
      <Callout title="This marker is draggable!" />
    </PointAnnotation>
  );
};

Custom Callouts

Add custom callout views that appear when annotation is selected:
import { PointAnnotation, Callout } from '@rnmapbox/maps';
import { Text, View } from 'react-native';

<PointAnnotation
  id="callout-marker"
  coordinate={[-73.99155, 40.73581]}
  title="Marker with Callout"
>
  <View style={{
    width: 30,
    height: 30,
    borderRadius: 15,
    backgroundColor: 'green',
  }} />
  
  <Callout title="Custom Callout">
    <View style={{ padding: 10 }}>
      <Text style={{ fontWeight: 'bold' }}>Custom Content</Text>
      <Text>You can add any React Native views here</Text>
    </View>
  </Callout>
</PointAnnotation>

Anchoring

Control which part of your custom view aligns with the coordinate:
<PointAnnotation
  id="pin"
  coordinate={[-73.99155, 40.73581]}
  anchor={{ x: 0.5, y: 0 }} // Top center
>
  <PinIcon />
</PointAnnotation>

Remote Images

When using remote images, call refresh() after the image loads:
import { useRef } from 'react';
import { Image } from 'react-native';
import { PointAnnotation } from '@rnmapbox/maps';

const RemoteImageAnnotation = () => {
  const annotationRef = useRef<PointAnnotation>(null);

  return (
    <PointAnnotation
      ref={annotationRef}
      id="remote-image"
      coordinate={[-73.99155, 40.73581]}
    >
      <Image
        source={{ uri: 'https://example.com/marker.png' }}
        style={{ width: 40, height: 40 }}
        onLoad={() => annotationRef.current?.refresh()}
        fadeDuration={0} // Prevent animation artifacts
      />
    </PointAnnotation>
  );
};
Always set fadeDuration={0} on Image components in PointAnnotation to prevent rendering the bitmap at an unknown animation state.

MarkerView

MarkerView renders actual React Native views as map annotations, providing full interactivity but with performance trade-offs.

Basic Usage

import { MapView, MarkerView } from '@rnmapbox/maps';
import { View, Text, TouchableOpacity } from 'react-native';

const InteractiveMarkers = () => {
  return (
    <MapView style={{ flex: 1 }}>
      <MarkerView coordinate={[-73.99155, 40.73581]}>
        <TouchableOpacity
          onPress={() => console.log('Marker pressed!')}
          style={{
            backgroundColor: 'white',
            padding: 10,
            borderRadius: 8,
            borderWidth: 2,
            borderColor: 'blue',
          }}
        >
          <Text style={{ fontWeight: 'bold' }}>Interactive!</Text>
          <Text>Tap me</Text>
        </TouchableOpacity>
      </MarkerView>
    </MapView>
  );
};

Props

PropTypeDefaultDescription
coordinate[longitude, latitude]RequiredPosition on the map
anchor{ x: number, y: number }{ x: 0.5, y: 0.5 }Anchor point in [0, 1] space
allowOverlapbooleanfalseAllow markers to overlap
allowOverlapWithPuckbooleanfalseAllow overlap with user location puck
isSelectedbooleanfalseSelection state
childrenReactElementRequiredReact Native view to display

Handling Overlapping Markers

const [allowOverlapWithPuck, setAllowOverlapWithPuck] = useState(false);

<MarkerView
  coordinate={[-73.99155, 40.73581]}
  allowOverlap={true} // Don't collapse with other markers
  allowOverlapWithPuck={allowOverlapWithPuck} // Control puck overlap
>
  <CustomMarkerView />
</MarkerView>
When allowOverlap is false, markers that are close together will collapse and only one will be shown.

Full Interactivity Example

import { useState } from 'react';
import { MarkerView } from '@rnmapbox/maps';
import { View, Text, TouchableOpacity, Button } from 'react-native';

const FullyInteractiveMarker = () => {
  const [count, setCount] = useState(0);

  return (
    <MarkerView coordinate={[-73.99155, 40.73581]}>
      <View style={{
        backgroundColor: 'white',
        padding: 15,
        borderRadius: 10,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.25,
        shadowRadius: 4,
      }}>
        <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
          Count: {count}
        </Text>
        <TouchableOpacity
          onPress={() => setCount(count + 1)}
          style={{
            backgroundColor: 'blue',
            padding: 8,
            borderRadius: 5,
            marginTop: 8,
          }}
        >
          <Text style={{ color: 'white', textAlign: 'center' }}>+1</Text>
        </TouchableOpacity>
      </View>
    </MarkerView>
  );
};

Dynamic Annotations

Add annotations dynamically based on user interaction:
import { useState } from 'react';
import { MapView, PointAnnotation } from '@rnmapbox/maps';

const DynamicAnnotations = () => {
  const [annotations, setAnnotations] = useState<GeoJSON.Position[]>([]);

  return (
    <MapView
      style={{ flex: 1 }}
      onPress={(feature) => {
        const coords = (feature.geometry as GeoJSON.Point).coordinates;
        setAnnotations([...annotations, coords]);
      }}
    >
      {annotations.map((coordinate, index) => (
        <PointAnnotation
          key={`annotation-${index}`}
          id={`annotation-${index}`}
          coordinate={coordinate}
        >
          <View style={{
            width: 20,
            height: 20,
            borderRadius: 10,
            backgroundColor: 'red',
          }} />
        </PointAnnotation>
      ))}
    </MapView>
  );
};

Clustering Annotations

For large numbers of markers, use ShapeSource with clustering:
import { ShapeSource, SymbolLayer, CircleLayer } from '@rnmapbox/maps';

const ClusteredMarkers = () => {
  const geoJSON = {
    type: 'FeatureCollection',
    features: locations.map((loc, i) => ({
      type: 'Feature',
      id: `marker-${i}`,
      geometry: {
        type: 'Point',
        coordinates: loc,
      },
      properties: { name: `Location ${i}` },
    })),
  };

  return (
    <ShapeSource
      id="markers"
      shape={geoJSON}
      cluster={true}
      clusterRadius={50}
      clusterMaxZoomLevel={14}
    >
      {/* Cluster circles */}
      <CircleLayer
        id="clusters"
        filter={['has', 'point_count']}
        style={{
          circleColor: '#51bbd6',
          circleRadius: 20,
        }}
      />

      {/* Individual markers */}
      <SymbolLayer
        id="markers"
        filter={['!', ['has', 'point_count']]}
        style={{
          iconImage: 'marker-icon',
          iconSize: 1,
        }}
      />
    </ShapeSource>
  );
};

Layer Ordering

Control annotation z-order relative to other layers:
import { getAnnotationsLayerID, FillLayer } from '@rnmapbox/maps';

<FillLayer
  id="polygon"
  belowLayerID={getAnnotationsLayerID('PointAnnotations')}
  style={{
    fillColor: 'rgba(255, 0, 0, 0.5)',
  }}
/>

Performance Tips

  • Use ShapeSource + SymbolLayer for 100+ static markers
  • Limit MarkerView usage to ~100 markers maximum
  • Disable animations in PointAnnotation images with fadeDuration={0}
  • Call refresh() on PointAnnotation when updating remote images
  • Use clustering for large datasets

Reference

  • PointAnnotation: src/components/PointAnnotation.tsx:1
  • MarkerView: src/components/MarkerView.tsx:1
  • Callout: src/components/Callout.tsx:1

Build docs developers (and LLMs) love