⚠️ Early Stage Project: This library is not production-ready yet. Developers should use it with caution and expect breaking changes.

Comparison

See the difference between imperative and declarative map development

Building interactive maps with raw Mapbox GL JS requires a lot of imperative code, DOM manipulation, and careful state management. Terrae simplifies this with a declarative, component-based approach that feels natural in React.

Example

Let's create a map with a marker, popup, route line, and controls. Both approaches produce the exact same result:

Without Terrae

Using Mapbox GL JS directly requires managing the map lifecycle, creating DOM elements manually, and writing imperative code for each feature.

import mapboxgl from 'mapbox-gl';

mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v12',
  center: [-74.006, 40.7128],
  zoom: 12,
});

map.on('load', () => {
  // Add marker
  const el = document.createElement('div');
  el.id = 'marker';
  el.style.backgroundImage = 'url(marker.png)';
  el.style.width = '32px';
  el.style.height = '32px';
  el.style.backgroundSize = '100%';
  el.style.cursor = 'pointer';

  const marker = new mapboxgl.Marker(el)
    .setLngLat([-74.006, 40.7128])
    .addTo(map);

  // Add popup
  const popup = new mapboxgl.Popup({ offset: 25 })
    .setLngLat([-74.006, 40.7128])
    .setHTML('<h3>New York City</h3><p>The city that never sleeps</p>');

  el.addEventListener('click', () => {
    popup.addTo(map);
  });

  // Add line
  map.addSource('route', {
    type: 'geojson',
    data: {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: [
          [-74.006, 40.7128],
          [-73.9857, 40.7484],
          [-73.9772, 40.7527],
        ],
      },
    },
  });

  map.addLayer({
    id: 'route',
    type: 'line',
    source: 'route',
    layout: {
      'line-join': 'round',
      'line-cap': 'round',
    },
    paint: {
      'line-color': '#3b82f6',
      'line-width': 4,
    },
  });

  // Add controls
  map.addControl(new mapboxgl.NavigationControl());
  map.addControl(
    new mapboxgl.FullscreenControl()
  );
});

With Terrae

Terrae provides a React-friendly, declarative API. Simply compose components together and the library handles all the complexity under the hood.

import { Map, MapMarker, MarkerContent, MarkerPopup, MapLine, MapControls, MapZoom, MapFullscreen } from "@/registry/map";

export function MyMap() {
  const route = [
    [-74.006, 40.7128],
    [-73.9857, 40.7484],
    [-73.9772, 40.7527],
  ];

  return (
    <Map
      accessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN!}
      center={[-74.006, 40.7128]}
      zoom={12}
    >
      <MapMarker coordinates={[-74.006, 40.7128]}>
        <MarkerContent>
          <div className="size-8 rounded-full bg-blue-500 shadow-lg" />
        </MarkerContent>
        <MarkerPopup>
          <h3>New York City</h3>
          <p>The city that never sleeps</p>
        </MarkerPopup>
      </MapMarker>

      <MapLine 
        coordinates={route} 
        color="#3b82f6" 
        width={4} 
      />

      <MapControls position="bottom-right">
        <MapZoom />
        <MapFullscreen />
      </MapControls>
    </Map>
  );
}

Key Differences

Without TerraeWith Terrae
Manual DOM creation and manipulationDeclarative React components
Imperative API with many setup stepsFamiliar React patterns and hooks
Complex lifecycle managementAutomatic lifecycle management
Boilerplate code for common tasksMinimal code for common tasks
Difficult to compose features togetherEasy to compose components
No built-in theme supportAutomatic light/dark theme support
Limited type safetyType-safe with full TypeScript support