PACKAGE

@element-editor/react

Element Editor's self-contained map editor in one React component. Owns the map, the toolbar, and all editor state. Zero configuration to start.

This is what mounting a single <ElementEditor /> component gives you.

Install

npm install @element-editor/react maplibre-gl

Usage

import { ElementEditor } from '@element-editor/react'
import '@element-editor/react/style.css' // includes MapLibre's CSS

export default function App() {
  return (
    <div style={{ height: 600 }}>
      <ElementEditor
        onChange={(data) => console.log(data)}
      />
    </div>
  )
}

That's it. The component mounts a MapLibre map with the Streets basemap (Dark in dark mode), renders the toolbar, and is ready to draw. No provider setup, no route configuration, no map-instance lifecycle management.

Using Leaflet instead

The default <ElementEditor> runs on MapLibre. (In the unpublished 0.1 pre-release it ran on Leaflet; see Migrating to 0.2 if you built against it.) For the Leaflet version, install @element-editor/adapter-leaflet leaflet react-leaflet and import from the /leaflet entry:

import { ElementEditor } from '@element-editor/react/leaflet'
import '@element-editor/react/leaflet/style.css'

The props and ref are the same. tileLayer and renderer exist on the Leaflet entry only; on MapLibre use basemap and basemaps.

When to pick this package

Use @element-editor/react when:

  • You don't have a map yet, or you're starting a new project with no constraint on the map library.
  • You want everything assembled, including the toolbar, layers panel, data table, schema editor, and GeoJSON viewer.
  • You don't need to customise the map layout. The component owns its viewport.

Use @element-editor/adapter-maplibre or @element-editor/adapter-leaflet instead when you already have a map instance you want to keep.

Key props

PropTypeDescription
initialDataFeatureCollectionInput | GeoJSONFeatureInput | nullPre-populate with existing GeoJSON. Also applied if it arrives after mount, while the editor is still empty.
onChange(data: FeatureCollection) => voidFires on every committed change.
onFeatureAdd(feature: GeoJSONFeature) => voidFires once per newly added feature.
onFeatureUpdate(id: string, feature: GeoJSONFeature) => voidFires on commit (not during drag).
onFeatureDelete(id: string) => voidFires once per deleted feature.
onSelectionChange(ids: string[]) => voidFires whenever selection changes.
onValidationError(errors: Record<string, string[]>) => voidFires when a submit is blocked by schema errors.
toolsDrawTool[]Restrict which tools appear in the toolbar.
schemaSchemaPre-load a single schema.
readonlybooleanBlock every user edit. The ref keeps working.
theme'light' | 'dark' | 'auto'Light or dark mode only. Colours go through accentColor, style or CSS.
accentColorstringBrand colour, any CSS colour. The UI and map highlights follow it.
featureColorstringDefault colour of every feature, any CSS colour. Colour single features with getFeatureStyle.
styleReact.CSSPropertiesOn the editor root; accepts CSS variables (--elm-accent, design tokens like --elm-surface), which also reach Element's portals.
defaultBasemapBasemapInputStarting basemap: 'streets', 'terrain', 'light', 'dark', 'osm', 'auto' (default; follows the theme), a raster { url, attribution } or a MapLibre { style }. Use basemap + onBasemapChange to control it.
basemapsBasemapInput[] | falseChoices in the basemap picker. false hides it.
maxFeaturesnumberCap the feature count.

Full prop reference and ref API in the docs.

Imperative API via ref

import { useRef } from 'react'
import { ElementEditor, type ElementEditorRef } from '@element-editor/react'

export default function App() {
  const ref = useRef<ElementEditorRef>(null)

  return (
    <>
      <div style={{ height: 600 }}>
        <ElementEditor ref={ref} />
      </div>
      <button onClick={() => ref.current?.getFeatureCollection()}>
        Export
      </button>
      <button onClick={() => ref.current?.undo()}>Undo</button>
    </>
  )
}

ElementEditorRef methods include getFeatureCollection, getFeature, setFeatureCollection, importGeoJSON, exportGeoJSON, addFeature, updateFeature(s), removeFeatures, select, setTool / getTool, getMap, getBasemap / setBasemap, flyTo, fitBounds, undo / redo, canUndo / canRedo, validate and submit.

Basemaps

Five basemaps are built in (Streets, Terrain, Light, Dark, OpenStreetMap), the vector ones served from OpenFreeMap, all with no API key, and a picker lets users switch. 'auto' follows the editor theme. Pass defaultBasemap to choose the starting one (or basemap + onBasemapChange to control it from your app), and basemaps to set what the picker offers:

import { ElementEditor } from '@element-editor/react'

<ElementEditor
  defaultBasemap="auto"
  basemaps={[
    'auto', 'streets', 'dark',
    {
      id: 'satellite',
      label: 'Satellite',
      url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
      attribution: 'Tiles © Esri',
    },
  ]}
/>

Pass basemaps={false} to hide the picker. Any raster tile URL or MapLibre style works. See the basemaps guide and map styles.

What's included

Everything from the Element Editor feature set, pre-assembled:

Versions and dependencies

  • React and React DOM ^19.2 — React 18 is not supported (the data table needs 19.2).
  • TypeScript — types ship in the package. With the /leaflet entry, also install @types/leaflet.
  • Peer deps: react, react-dom ^19.2, maplibre-gl ^5.5. For the /leaflet entry, also install @element-editor/adapter-leaflet, leaflet ^1.9 and react-leaflet ^5 (optional peers).
  • Dependencies: @element-editor/core, @element-editor/ui and @element-editor/adapter-maplibre are installed alongside, not bundled. ES modules plus .cjs; the geometry worker is inlined, so no bundler configuration is needed.
  • CSS: the stylesheet only styles the editor; it doesn't reset your page.

Ready to build?

Read the quickstart →

Open the playground · Full API reference · See the adapter-maplibre package