PACKAGE

@element-editor/adapter-maplibre

Plug editing into MapLibre GL JS, MapTiler SDK, or Mapbox GL JS. One adapter covers them all — they share API lineage.

Vector tiles from MapLibre, editing layer from Element Editor.

Install

npm install @element-editor/adapter-maplibre @element-editor/ui maplibre-gl

For Mapbox GL JS or MapTiler SDK, install that package as well. Keep maplibre-gl: the adapter imports it itself.

Usage

import { useEffect, useRef, useState } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { ElementEditorUIProvider, GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-maplibre'
import '@element-editor/adapter-maplibre/style.css'

export default function MyMap() {
  const container = useRef<HTMLDivElement>(null)
  const [map, setMap] = useState<maplibregl.Map | null>(null)

  useEffect(() => {
    const m = new maplibregl.Map({
      container: container.current!,
      style: 'https://demotiles.maplibre.org/style.json',
      center: [2.35, 48.86],
      zoom: 12,
    })
    setMap(m)
    return () => { m.remove(); setMap(null) }
  }, [])

  return (
    <ElementEditorUIProvider>
      <GeoStoreProvider>
        <div data-elm-theme="light" style={{ position: 'relative', height: 600 }}>
          <div ref={container} style={{ width: '100%', height: '100%' }} />
          {map && (
            <ElementEditorControls
              map={map}
              onChange={(data) => console.log(data)}
            />
          )}
        </div>
      </GeoStoreProvider>
    </ElementEditorUIProvider>
  )
}

ElementEditorUIProvider supplies the editor's contexts and its own toast <Toaster>. The data-elm-theme element scopes Element's styles and keyboard shortcuts. ElementEditorControls waits for the style to accept sources, so it can mount as soon as the map exists.

To brand the editor, pass one colour: <ElementEditorUIProvider accentColor="#ea580c">. The UI and the map highlights follow it. Recolour the features with featureColor or per feature with getFeatureStyle. See Theming.

Pass the map instance to ElementEditorControls via the map prop, and keep it in React state so the component renders once the map exists. That's the one extra step over the Leaflet adapter, where ElementEditorControls finds the map through react-leaflet's context.

When to pick this package

Use @element-editor/adapter-maplibre when:

  • Your app is on MapLibre GL JS — the open-source fork of Mapbox GL JS.
  • Your app is on MapTiler SDK — MapTiler's wrapper around the same engine family.
  • Your app is on Mapbox GL JS v3+ — the commercial engine.

They share enough API surface that one adapter covers them all. The map prop accepts any of them.

Use @element-editor/react for a drop-in MapLibre editor that owns its map, or @element-editor/adapter-leaflet for Leaflet-specific plug-in.

The map-load timing pattern

Vector tile engines need their style to finish loading before layers can be added. You don't have to time this yourself: ElementEditorControls waits until the style can accept sources (it retries on styledata and load) and puts its layers back after every map.setStyle(). Waiting for load before you hand over the map is still the safe pattern:

const [map, setMap] = useState<maplibregl.Map | null>(null)

useEffect(() => {
  const m = new maplibregl.Map({ /* ... */ })
  m.on('load', () => setMap(m))
  return () => { setMap(null); m.remove() }
}, [])

{map && <ElementEditorControls map={map} />}

A map held only in a useRef doesn't re-render your component, so keep it in state.

Key components and props

Same as @element-editor/adapter-leaflet, with one addition:

PropTypeDescription
mapmaplibregl.MapThe map instance. Required. A MapTiler SDK map works as is; a Mapbox GL JS map needs a TypeScript cast (map as any).

The other props (onChange, onFeatureAdd, tools, schema, readonly, etc.) match the Leaflet adapter, with three differences: maxBounds instead of bounds, a children prop for @element-editor/ui components, and no renderer. See the full reference.

Also exports a useMapLibre hook for components deeper in the tree that need the map instance without prop drilling.

Basemaps

@element-editor/adapter-maplibre/basemaps is the same built-in catalog the drop-in editor uses: streets, terrain, light, dark (vector, OpenFreeMap) and osm (raster), plus 'auto', which follows the editor theme. No API key.

import { loadBasemapStyle, setBasemap } from '@element-editor/adapter-maplibre/basemaps'

const map = new maplibregl.Map({ container, style: await loadBasemapStyle('auto', { theme: 'light' }) })
await setBasemap(map, 'terrain') // switches style and keeps Element's layers

Render BasemapControl (from the main entry) for a ready-made picker. See the basemaps guide and map styles.

Mapbox GL JS compatibility

Mapbox GL JS v3+ is API-compatible with MapLibre GL JS at runtime for the operations Element relies on. The same adapter works for either; TypeScript needs a cast, because the two packages' type names differ:

import mapboxgl from 'mapbox-gl'
import 'mapbox-gl/dist/mapbox-gl.css'
import { GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-maplibre'

mapboxgl.accessToken = 'YOUR_TOKEN'

const map = new mapboxgl.Map({
  container: /* ... */,
  style: 'mapbox://styles/mapbox/streets-v12',
})

// Same pattern: wait for load, then <ElementEditorControls map={map as any} />

MapTiler SDK compatibility

MapTiler SDK also works:

import { Map as MapTilerMap } from '@maptiler/sdk'
import '@maptiler/sdk/dist/maptiler-sdk.css'
import { GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-maplibre'

const map = new MapTilerMap({
  container: /* ... */,
  apiKey: 'YOUR_KEY',
})

What's included

Same feature set as the Leaflet adapter:

Versions and dependencies

  • React and React DOM ^19.2 (React 18 is not supported)
  • maplibre-gl ^5.5 (peer); Mapbox GL JS and MapTiler SDK instances also work at runtime
  • @element-editor/core and @element-editor/ui come in as regular dependencies; no bundler configuration is needed (the geometry worker is inlined). DrawingCursorHint is no longer exported.

Types ship in the package.

Ready to plug it in?

Read the quickstart →

Open the playground · Full API reference · vs Mapbox GL Draw