PACKAGE

@element-editor/ui

Individual UI components — toolbars, panels, modals — for building custom editor layouts with Element's adapters.

Same components, positioned wherever your app's layout wants them.

Install

npm install @element-editor/ui @element-editor/adapter-maplibre maplibre-gl
# or
npm install @element-editor/ui @element-editor/adapter-leaflet leaflet react-leaflet

The adapter packages already depend on @element-editor/ui; list it in your own package.json too so you can import from it. Its styles ship in the adapter stylesheet (@element-editor/adapter-leaflet/style.css or @element-editor/adapter-maplibre/style.css) — there is no @element-editor/ui/style.css.

Usage — custom editor layout

import { Suspense, useState } from 'react'
import { MapContainer, TileLayer } from 'react-leaflet'
import { ElementEditorUIProvider, GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-leaflet'
import {
  FloatingToolbar,
  EditingToolbar,
  LayersPanel,
  DataTablePanel,
  SchemaModal,
  GeoJSONModal,
  useUIStore,
} from '@element-editor/ui'
import '@element-editor/adapter-leaflet/style.css'

// Schema and GeoJSON modals render whenever mounted, so mount them only while open
function Modals() {
  const schemaOpen = useUIStore((s) => s.schemaModalOpen)
  const toggleSchema = useUIStore((s) => s.toggleSchemaModal)
  const codeOpen = useUIStore((s) => s.codeModalOpen)
  const toggleCode = useUIStore((s) => s.toggleCodeModal)
  return (
    <>
      {schemaOpen && <Suspense fallback={null}><SchemaModal onClose={toggleSchema} /></Suspense>}
      {codeOpen && <Suspense fallback={null}><GeoJSONModal onClose={toggleCode} /></Suspense>}
    </>
  )
}

export default function CustomEditor() {
  const [mapReady, setMapReady] = useState(false)

  return (
    <ElementEditorUIProvider>
      <GeoStoreProvider>
        <div data-elm-theme="light" style={{ position: 'relative', height: 600 }}>
          <MapContainer
            center={[48.86, 2.35]}
            zoom={12}
            style={{ position: 'absolute', inset: 0 }}
            whenReady={() => setMapReady(true)}
          >
            <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
            {mapReady && <ElementEditorControls />}
          </MapContainer>

          {/* UI goes outside MapContainer, inside the data-elm-theme element */}
          {mapReady && (
            <>
              <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>
              <Modals />
            </>
          )}
        </div>
      </GeoStoreProvider>
    </ElementEditorUIProvider>
  )
}

Each component connects to the shared store via GeoStoreProvider; the data table and the modals are lazy-loaded, so each sits in its own <Suspense>. Place them wherever your layout demands — sidebar, header, popout window, floating over the map. State is coordinated automatically.

When to pick this package

Use @element-editor/ui when:

  • You're using an adapter (@element-editor/adapter-leaflet or @element-editor/adapter-maplibre) and need UI beyond the editing layer.
  • You need custom layout — your toolbar docks to your app sidebar, your data table lives in a popout, your property panel integrates with your existing forms.
  • You want to recreate the full @element-editor/react experience with your own layout. Every UI component used by @element-editor/react comes from @element-editor/ui; you can assemble the same experience with different placement.

Use @element-editor/react instead if the default layout fits your needs.

What's in the box

Toolbars

ComponentPurpose
FloatingToolbarThe drawing-tool toolbar. Shows the drawing tools (including the OSM picker) plus measure.
EditingToolbarThe selection-context toolbar. Shows boolean ops, geometry processing, transform.
ActiveDrawingToolbarContextual controls shown during active drawing (undo last vertex, cancel, finish).

Panels

ComponentPurpose
LayersPanelFeature list with grouping, visibility toggle, and selection.
FeatureSidePanelDetail panel for the selected feature — shows schema-backed property forms.
DataTablePanelSpreadsheet view of all features with inline editing.

Modals

ComponentPurpose
SchemaModalIn-app schema editor.
GeoJSONModalTwo-way GeoJSON editor — CodeMirror 6, validated on save.
ShortcutsModalKeyboard-shortcut customisation.
SettingsModalTheme, units, and editor preferences.

Miscellaneous

ComponentPurpose
FitBoundsButtonButton to zoom to the feature collection's extent.
BooleanOperationsToolbar subset — just the boolean-op buttons.
IconButton, Tooltip, Kbd, ButtonReusable primitives.

Hooks

HookPurpose
useDataStoreRead editor data (features, schemas, selection) with a selector.
useUIStoreAccess UI state (open modals, active mode, etc.).
useGeoStoreThe raw data and UI store instances, for imperative access.
useShortcutConfigRead the current key of each shortcut (users can remap them).
useThemeGet the resolved theme (handles auto resolution).
useAccentPaletteThe editor's accent colour as a 50–950 palette, for colouring your own map layers.
useAccentStyleThe --elm-accent style for your own portals, so they match the editor.
useMapControllerAccess the map controller (fly to, fit bounds) from inside ElementEditorUIProvider.

Full export list in the reference docs.

Recreating @element-editor/react with adapter + ui

@element-editor/react is itself assembled from an adapter + @element-editor/ui + a pre-configured map (@element-editor/adapter-maplibre for the default entry, @element-editor/adapter-leaflet for @element-editor/react/leaflet). Here is the Leaflet version; the MapLibre guide shows the same on MapLibre. If you want the same experience with your own layout:

import { Suspense } from 'react'
import { MapContainer, TileLayer } from 'react-leaflet'
import { ElementEditorUIProvider, GeoStoreProvider, ElementEditorControls } from '@element-editor/adapter-leaflet'
import {
  FloatingToolbar,
  EditingToolbar,
  ActiveDrawingToolbar,
  LayersPanel,
  DataTablePanel,
  ShortcutsModal,
  SettingsModal,
  FitBoundsButton,
} from '@element-editor/ui'
import '@element-editor/adapter-leaflet/style.css'

<ElementEditorUIProvider>
  <GeoStoreProvider>
    <div data-elm-theme="light" style={{ position: 'relative', height: 600, overflow: 'hidden' }}>
      <MapContainer center={[48.86, 2.35]} zoom={12} style={{ position: 'absolute', inset: 0 }}>
        <TileLayer url="https://tile.openstreetmap.org/{z}/{x}/{y}.png" />
        <ElementEditorControls />
      </MapContainer>
      <FloatingToolbar />
      <div style={{ position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 400, display: 'flex', gap: 8 }}>
        <FitBoundsButton />
        <EditingToolbar />
        <ActiveDrawingToolbar />
      </div>
      <LayersPanel />
      <Suspense fallback={null}><DataTablePanel /></Suspense>
      <Suspense fallback={null}><ShortcutsModal /></Suspense>
      <Suspense fallback={null}><SettingsModal /></Suspense>
      <Modals /> {/* the schema and GeoJSON modals, mounted while open (see above) */}
    </div>
  </GeoStoreProvider>
</ElementEditorUIProvider>

Things to know before you ship

  • @element-editor/ui components require ElementEditorUIProvider and GeoStoreProvider. They won't render outside them — the components subscribe to the shared store. Mount GeoStoreProvider once per editor instance.
  • Render everything inside a data-elm-theme element. The stylesheet doesn't reset your page, so Element's base styles only apply inside [data-elm-theme] and Element's own portals (which render to document.body). Your own portaled UI should carry data-elm-portal so it's styled and keyboard shortcuts keep working while it has focus.
  • Toasts and events. ElementEditorUIProvider renders its own <Toaster>; pass onEvent to receive every message yourself (the editor then shows no toasts).
  • @element-editor/ui is map-agnostic. The components don't assume Leaflet or MapLibre and need no map library. The adapter packages provide the map-specific glue.

Versions and dependencies

  • React and React DOM ^19.2 (React 18 is not supported; the data table needs 19.2)
  • @element-editor/core (installed with this package)

Ready to customise?

Read the custom UI guide →