If you've ever used mapbox-gl-draw or any MapLibre drawing library, you've probably
hit this frustrating limitation: all your drawn shapes share the same color.
Want polygon A in red and polygon B in green? Not straightforward. I ran into this
problem while building a GIS tool and couldn't find a clean solution β so I built one.
The problem
The default drawing libraries for MapLibre GL JS apply one global style to every
feature. If you try to set colors per feature using user_color properties, you
quickly discover it doesn't work reliably, and there are GitHub issues going back
to 2017 from developers hitting the same wall.
What I built
maplibre-gl-multiple-color-draw
β a lightweight drawing library where every shape keeps its own individual color
and thickness.
π Live Demo
Features:
- 6 drawing modes: Line, Dashed Line, Freehand, Freehand Dashed, Polygon, Select/Move
- Each feature has its own color and thickness β set it before you draw
- Change color and thickness at runtime with
setColor()andsetThickness() - Export everything as standard GeoJSON
- Built-in React hook (
useMapDraw) - Full TypeScript support
- Zero dependencies (only peer dep:
maplibre-gl)
Installation
npm install maplibre-gl-multiple-color-draw
npm install maplibre-gl
Basic React example
import { useEffect, useRef, useState } from "react";
import maplibregl, { Map as MaplibreMap } from "maplibre-gl";
import { useMapDraw } from "maplibre-gl-multiple-color-draw";
import "maplibre-gl/dist/maplibre-gl.css";
function MapDrawComponent() {
const mapContainer = useRef<HTMLDivElement>(null);
const mapRef = useRef<MaplibreMap | null>(null);
const [map, setMap] = useState<MaplibreMap | null>(null);
const [color, setColorState] = useState("#3388ff");
const [thickness, setThicknessState] = useState(2);
useEffect(() => {
if (!mapContainer.current || mapRef.current) return;
const newMap = new maplibregl.Map({
container: mapContainer.current,
style: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
center: [0, 0],
zoom: 2,
});
mapRef.current = newMap;
setMap(newMap);
return () => { newMap.remove(); mapRef.current = null; };
}, []);
const { enable, setMode, setColor, setThickness, getGeoJSON, clear } =
useMapDraw(map, { color, thickness });
useEffect(() => { if (map) enable(); }, [map, enable]);
return (
<div>
<div style={{ display: "flex", gap: "8px", padding: "10px" }}>
<button onClick={() => setMode("line")}>Line</button>
<button onClick={() => setMode("freehand")}>Freehand</button>
<button onClick={() => setMode("polygon")}>Polygon</button>
<button onClick={() => setMode("select")}>Select</button>
<input
type="color"
value={color}
onChange={(e) => { setColorState(e.target.value); setColor(e.target.value); }}
/>
<input
type="range" min="1" max="10" value={thickness}
onChange={(e) => { setThicknessState(Number(e.target.value)); setThickness(Number(e.target.value)); }}
/>
<button onClick={() => clear()}>Clear</button>
</div>
<div ref={mapContainer} style={{ width: "100%", height: "500px" }} />
</div>
);
}
export default MapDrawComponent;
Pick a color β draw a shape β pick a different color β draw another shape. Each one
keeps its own style independently.
Export as GeoJSON
When you're done drawing, get everything as standard GeoJSON:
const geoJSON = getGeoJSON();
// ready to send to your API or download
Links
- π¦ npm package
- π» GitHub
- π Live demo
It's new and I'd love feedback β drop a comment if you try it or run into any issues!
Top comments (0)