PACKAGE

@element-editor/adapter-leaflet

Plug editing into an existing Leaflet or react-leaflet map. You control the map; Element controls the editing layer.

Your existing Leaflet map stays yours. Element attaches as a layer.

Install

npm install @element-editor/adapter-leaflet @element-editor/ui leaflet react-leaflet

With TypeScript, also npm install -D @types/leaflet.

Usage

import { MapContainer, TileLayer } from 'react-leaflet'
import { ElementEditorUIProvider, GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-leaflet'
import '@element-editor/adapter-leaflet/style.css' // includes Leaflet's CSS

export default function MyMap() {
  return (
    <ElementEditorUIProvider>
      <GeoStoreProvider>
        <div data-elm-theme="light">
          <MapContainer center={[48.86, 2.35]} zoom={12} style={{ height: 600 }}>
            <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
            <ElementEditorControls
              onChange={(data) => console.log(data)}
            />
          </MapContainer>
        </div>
      </GeoStoreProvider>
    </ElementEditorUIProvider>
  )
}

ElementEditorUIProvider supplies the editor's contexts and its own toast <Toaster>; GeoStoreProvider holds the editor state and must wrap MapContainer. The data-elm-theme element scopes Element's styles (the stylesheet doesn't reset your page) and its keyboard shortcuts. ElementEditorControls must be inside MapContainer. The adapter attaches the editing layer to your existing map — no control is taken away, no event is hijacked.

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.

When to pick this package

Use @element-editor/adapter-leaflet when:

  • You already have a Leaflet or react-leaflet map and want to add editing without replacing your map setup.
  • You need custom layout — your toolbar lives in your app sidebar, your data panels in your own UI.
  • You want to keep Leaflet's ecosystem — plugins, heatmaps, clusterers, custom layers — alongside the editing tools.

Use @element-editor/react instead if you want a drop-in that owns the map too (MapLibre by default). Want that drop-in on Leaflet? Import it from @element-editor/react/leaflet; it uses this adapter.

Key components

ComponentPurpose
ElementEditorUIProviderContext stack: error boundary, config, shortcuts, events and toasts, map controller. Wraps everything.
GeoStoreProviderContext provider. Wraps the map; stores all editor state.
ElementEditorControlsThe editing layer itself. Goes inside MapContainer.
FeatureInteractionLayerDraws the features and the selected-feature interactions (vertices, handles, overlays). Handled internally.
DrawingLayerActive drawing overlays. Handled internally.
SnapControllerSnapping logic. Enabled by default.

You import only what you need. The adapter package re-exports ElementEditorUIProvider, GeoStoreProvider and the toolbars from @element-editor/ui; install @element-editor/ui yourself when you import its panels.

Bringing your own UI

By default, ElementEditorControls does not render a visible toolbar or panels — just the editing interaction layer on the map. You add UI by installing @element-editor/ui and placing components wherever your layout needs them:

import { Suspense } from 'react'
import { MapContainer, TileLayer } from 'react-leaflet'
import {
  ElementEditorUIProvider,
  GeoStoreProvider,
  ElementEditorControls,
} from '@element-editor/adapter-leaflet'
import {
  FloatingToolbar,
  EditingToolbar,
  LayersPanel,
  DataTablePanel,
} from '@element-editor/ui'

<ElementEditorUIProvider>
  <GeoStoreProvider>
    <div data-elm-theme="light" style={{ position: 'relative', height: 600, overflow: 'hidden' }}>
      <MapContainer center={[48.86, 2.35]} zoom={12} style={{ height: '100%' }}>
        <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
        <ElementEditorControls />
      </MapContainer>
      {/* UI goes outside MapContainer, inside the data-elm-theme element */}
      <FloatingToolbar />
      <div style={{ position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 400, display: 'flex', gap: 8 }}>
        <EditingToolbar />
      </div>
      <LayersPanel />
      <Suspense fallback={null}><DataTablePanel /></Suspense>
    </div>
  </GeoStoreProvider>
</ElementEditorUIProvider>

ElementEditorControls goes inside MapContainer; the panels go outside it, or Leaflet treats their clicks and scrolls as map gestures. The data table is lazy-loaded, so it sits in its own <Suspense>.

See the custom UI guide for the full component list.

Key props on ElementEditorControls

PropTypeDescription
initialDataFeatureCollectionInput | nullPre-populate with existing GeoJSON.
onChange(data: FeatureCollection) => voidFires on every committed change.
onFeatureAdd / onFeatureUpdate / onFeatureDeletegranular callbacksFires per-feature events.
onSelectionChange(ids: string[]) => voidFires whenever selection changes.
toolsDrawTool[]Restrict the available drawing tools.
schemaSchemaPre-load a single schema.
readonlybooleanBlock every user edit. The ref keeps working.
onError(error) => void | false | stringEvery runtime failure, with a stable code and source.
onReady(map: L.Map) => voidThe map, once Element's layers are on it.

Full reference in the docs.

What's included

Everything in the Element feature set applies to this adapter:

A note on migrating from Leaflet.Draw or Geoman

Both Leaflet.Draw and Leaflet-Geoman attach to L.Control. Element's adapter attaches via react-leaflet's component model instead. Coexistence during migration is possible — ElementEditorControls doesn't replace or hijack existing controls. See the Leaflet.Draw comparison and Geoman comparison for migration snippets.

Versions and dependencies

  • React and React DOM ^19.2 (React 18 is not supported)
  • Leaflet ^1.9 (peer dep)
  • react-leaflet ^5 (peer dep)
  • @types/leaflet for TypeScript users
  • @element-editor/core and @element-editor/ui come in as regular dependencies; add them to your own package.json only if you import from them (for example the FeatureCollection type from @element-editor/core). No bundler configuration is needed: the geometry worker is inlined.

Types ship in the package. TypeScript-first.

Ready to plug it in?

Read the quickstart →

Open the playground · Full API reference · vs Leaflet.Draw