PACKAGE
@element-editor/ui
Individual UI components — toolbars, panels, modals — for building custom editor layouts with Element's adapters.
Screenshot: a custom editor layout — toolbar docked to the top bar of the host app (integrated with the app's own nav), layers panel in the left sidebar, data table in a collapsible bottom drawer, map taking the remaining centre space. Looks like a professional internal tool, not a default demo.
Install
npm install @element-editor/ui @element-editor/adapter-maplibre maplibre-gl
# or
npm install @element-editor/ui @element-editor/adapter-leaflet leaflet react-leafletThe 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-leafletor@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/reactexperience with your own layout. Every UI component used by@element-editor/reactcomes 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
| Component | Purpose |
|---|---|
FloatingToolbar | The drawing-tool toolbar. Shows the drawing tools (including the OSM picker) plus measure. |
EditingToolbar | The selection-context toolbar. Shows boolean ops, geometry processing, transform. |
ActiveDrawingToolbar | Contextual controls shown during active drawing (undo last vertex, cancel, finish). |
Panels
| Component | Purpose |
|---|---|
LayersPanel | Feature list with grouping, visibility toggle, and selection. |
FeatureSidePanel | Detail panel for the selected feature — shows schema-backed property forms. |
DataTablePanel | Spreadsheet view of all features with inline editing. |
Modals
| Component | Purpose |
|---|---|
SchemaModal | In-app schema editor. |
GeoJSONModal | Two-way GeoJSON editor — CodeMirror 6, validated on save. |
ShortcutsModal | Keyboard-shortcut customisation. |
SettingsModal | Theme, units, and editor preferences. |
Miscellaneous
| Component | Purpose |
|---|---|
FitBoundsButton | Button to zoom to the feature collection's extent. |
BooleanOperations | Toolbar subset — just the boolean-op buttons. |
IconButton, Tooltip, Kbd, Button | Reusable primitives. |
Hooks
| Hook | Purpose |
|---|---|
useDataStore | Read editor data (features, schemas, selection) with a selector. |
useUIStore | Access UI state (open modals, active mode, etc.). |
useGeoStore | The raw data and UI store instances, for imperative access. |
useShortcutConfig | Read the current key of each shortcut (users can remap them). |
useTheme | Get the resolved theme (handles auto resolution). |
useAccentPalette | The editor's accent colour as a 50–950 palette, for colouring your own map layers. |
useAccentStyle | The --elm-accent style for your own portals, so they match the editor. |
useMapController | Access 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/uicomponents requireElementEditorUIProviderandGeoStoreProvider. They won't render outside them — the components subscribe to the shared store. MountGeoStoreProvideronce per editor instance.- Render everything inside a
data-elm-themeelement. 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 todocument.body). Your own portaled UI should carrydata-elm-portalso it's styled and keyboard shortcuts keep working while it has focus. - Toasts and events.
ElementEditorUIProviderrenders its own<Toaster>; passonEventto receive every message yourself (the editor then shows no toasts). @element-editor/uiis 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)
Related
@element-editor/react— the pre-assembled version@element-editor/adapter-leaflet— Leaflet rendering layer@element-editor/adapter-maplibre— MapLibre / Mapbox / MapTiler rendering layer- Custom UI guide — full layout patterns