The Map component renders an interactive map using MapLibre GL JS (v6). Overlay layers — GeoJSON, CSV, Directus, a generic API, or raster XYZ/WMS tiles — can be added via the overlayLayers prop or shorthand props like geojson, csv, directus, etc. Layers can include searchable fields for advanced filtering.
Requirements: MapLibre GL JS v6 needs WebGL2 (supported by every current browser; the map can’t render where WebGL2 is unavailable or disabled). The MapLibre Web Worker is configured automatically by @lad-sapienza/scms-core, so a consuming site needs no bundler or worker setup of its own.
Live Examples
Simple map with base layers
The most basic Map configuration. Displays a map centered on coordinates 37,37 (Turkey) at zoom 8 with two switchable basemap layers.
Available basemap shorthand values: CAWM, OSM, EsriSatellite, EsriStreets, EsriTopo, GoogleSatellite, GoogleRoadmap, GoogleTerrain, GoogleAlteredRoadmap, CartoDB, StamenTerrain, OSMMapnick, OSMCycle.
<Map client:only="react" height="500px" center="37,37,8" baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Custom basemap configuration
Instead of shorthand keys, provide full configuration objects with name, url, and optional attribution:
<Map client:only="react" height="500px" center="37,37,8" baseLayers={[ { name: "Esri Topo", url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}", }, 'GoogleSatellite' ]}/>Adding map controls
Add navigation, scale, fullscreen, and layer-switcher controls using their respective props. Each accepts a position string (top-right, top-left, bottom-right, bottom-left).
<Map client:only="react" height="500px" center="37,37,8" navigationControl="top-left" scaleControl="bottom-left" fullscreenControl="top-right" layerControl="top-right" baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Load geo-data from a GeoJSON file
Use the geojson shorthand prop to load a GeoJSON file directly. fitToContent: true automatically zooms the map to show all features.
<Map client:only="react" height="500px" geojson={{ name: "Kahramanmaraş Survey Sites", path: "/data/ksa.geojson", popup: "<b>${Site_Name}</b><br/>${Description}", fitToContent: true, style: { type: 'circle', paint: { 'circle-radius': 4, 'circle-color': '#ff6b6b', 'circle-stroke-width': 1, 'circle-stroke-color': '#ffffff' } } }} baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Load geo-data from a CSV file
Load point data from a CSV file. Use lng and lat to specify which columns contain the coordinates.
<Map client:only="react" height="500px" center="37,37,8" csv={{ path: "/data/ksa.csv", lng: "Longitude", lat: "Latitude", popup: "<b>${Site_Name}</b><br/>${Description}", style: { type: 'circle', paint: { 'circle-radius': 8, 'circle-color': '#4ecdc4', 'circle-stroke-width': 2, 'circle-stroke-color': '#ffffff' } } }} baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Client-side filtering with GeoJSON
Filter features client-side using a Directus-like filter object. Supported operators: _eq, _neq, _in, _nin, _contains, _ncontains, _gt, _gte, _lt, _lte, _null, _nnull. Multiple conditions use AND logic.
<Map client:only="react" height="500px" geojson={{ path: "/data/ksa.geojson", name: "Middle Bronze Age Sites", filter: { Middle_Bronze: { _eq: "true" } }, popup: "<b>${Site_Name}</b><br/>${Description}", fitToContent: true, style: { type: 'circle', paint: { 'circle-radius': 6, 'circle-color': '#e74c3c', 'circle-stroke-width': 1, 'circle-stroke-color': '#ffffff' } } }} baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Multiple Overlay Layers
Add multiple overlay layers (GeoJSON and CSV) with independent styles and popups using the overlayLayers prop:
<Map client:only="react" height="600px" scaleControl="bottom-left" overlayLayers={[ { name: 'GeoJSON Sites', source: { type: 'geojson', url: '/data/ksa.geojson' }, popupTemplate: '<b>${Site_Name}</b><br/>${Description}', fitToContent: true, style: { type: 'circle', paint: { 'circle-radius': 8, 'circle-color': '#ff6b6b' } } }, { name: 'CSV Sites', source: { type: 'csv', url: '/data/ksa.csv' }, popupTemplate: '<b>${Site_Name}</b>', style: { type: 'circle', paint: { 'circle-radius': 4, 'circle-color': '#4ecdc4' } } } ]} baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Raster overlay independent of the basemap
A raster layer (XYZ or WMS tiles) can now be an independent, toggleable overlay instead of only a radio-exclusive basemap — e.g. a satellite or historical map layer shown on top of whichever basemap the user picks. Set source.type to xyz or wms on an overlayLayers entry; it renders as MapLibre raster tiles and shows up as a checkbox in the layer control, separate from the basemap radio group.
Both raster source types need the tile server to send CORS headers — MapLibre uploads tiles as WebGL textures, which browsers refuse cross-origin without a successful CORS check.
<Map client:only="react" height="500px" center="37,37,8" baseLayers={['EsriStreets']} overlayLayers={[ { name: 'Satellite', source: { type: 'xyz', url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' } }, { name: 'US States (WMS demo)', source: { type: 'wms', url: 'https://ahocevar.com/geoserver/wms', layers: 'topp:states' } } ]}/>Load map from MapLibre style JSON
Load a complete map configuration from a MapLibre style JSON file. The style file can include sources, layers, and full styling:
<Map client:only="react" height="500px" center="37,37,8" mapStyle="/data/ksa.json"/>Override and expand style JSON layers
Use styleOverrides to override paint/layout properties or add extra features (popupTemplate, fitToContent) to layers defined in a MapLibre style JSON:
<Map client:only="react" height="500px" mapStyle="/data/ksa.json" styleOverrides={{ 'ksa': { paint: { 'circle-color': '#00ff00', 'circle-radius': 8 }, popupTemplate: '<b>${Site_Name}</b>', fitToContent: true } }}/>Load data from Directus
Load geographical data directly from a Directus collection. geoField specifies which field contains GeoJSON geometry. Filter with Directus query syntax via queryString.
Credentials can be passed directly or via PUBLIC_DIRECTUS_URL / PUBLIC_DIRECTUS_TOKEN env vars.
<Map client:only="react" height="500px" center="37,37,8" directus={{ table: "scms_ksa", geoField: "geometry", queryString: "filter[Hellenistic_Roman][_eq]=true", name: "Hellenistic-Roman Sites", popup: "<b>${Site_Name}</b><br/>${Description}", fitToContent: true, style: { type: 'circle', paint: { 'circle-radius': 6, 'circle-color': '#9b59b6' } } }} baseLayers={['EsriSatellite', 'GoogleTerrain']}/>Overlay Layer Search
Overlay Layers with Search
Enable per-layer search by setting searchInFields on an overlay layer. A search icon appears in the layer control; clicking it opens a search interface supporting both simple free-text and advanced field/operator/value queries.
<Map client:only="react" height="600px" overlayLayers={[ { name: 'Archaeological Sites', source: { type: 'geojson', url: '/data/ksa.geojson' }, popupTemplate: '<b>${Site_Name}</b><br/>${Item_Label}', searchInFields: { Site_Name: "Site Name", Item_Label: "Item Label", Site_Description: "Description" }, fitToContent: true, style: { type: 'circle', paint: { 'circle-radius': 6, 'circle-color': '#ff6b6b' } } } ]} baseLayers={['EsriSatellite', 'OSM']}/>Props API
MapProps
| Prop | Type | Default | Description |
|---|---|---|---|
height | string | '400px' | Map height (CSS value) |
center | string | — | Initial center as "lng,lat,zoom" |
baseLayers | BaseLayerConfig[] | BasemapKey[] | — | Base layers (use shorthand keys or full config objects) |
overlayLayers | OverlayLayerConfig[] | — | Independent, toggleable overlay layers |
geojson | shorthand | — | Shorthand for a single GeoJSON layer |
csv | shorthand | — | Shorthand for a single CSV layer |
directus | shorthand | — | Shorthand for a single Directus layer |
mapStyle | string | object | — | MapLibre style JSON URL or object |
styleOverrides | Record<string, LayerOverride> | — | Override/extend layers from mapStyle |
navigationControl | ControlPosition | — | Navigation control position |
scaleControl | ControlPosition | — | Scale bar position |
fullscreenControl | ControlPosition | — | Fullscreen button position |
layerControl | boolean | ControlPosition | true | Layer switcher position |
geolocateControl | ControlPosition | — | Geolocate button position |
sprite | string | — | MapLibre sprite URL |
ControlPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
BaseLayerConfig
| Prop | Type | Description |
|---|---|---|
name | string | Label shown in the layer control |
url | string | Tile URL template, {z}/{x}/{y} (optionally with {s}, expanded automatically to a/b/c) |
attribution | string | Attribution HTML |
tileSize | number | Tile size in pixels (default: 256) |
OverlayLayerConfig
| Prop | Type | Description |
|---|---|---|
name | string | Layer display name |
source | SourceConfig | XyzSourceConfig | WmsSourceConfig | Data source — see below |
style | object | MapLibre paint/layout properties (row/feature sources only) |
popupTemplate | string | HTML popup with ${fieldName} placeholders (row/feature sources only) |
visible | boolean | Initial visibility (default: true) |
fitToContent | boolean | Auto-zoom to layer bounds (row/feature sources only) |
filter | FilterObject | function | Client-side feature filter (row/feature sources only) |
searchInFields | SearchInFields | Fields to expose in the search UI (row/feature sources only) |
source.type is geojson, csv, json, directus, or api for row/feature data (fetched and converted to GeoJSON), or xyz/wms for raster tiles (rendered natively via MapLibre, never fetched — style/popupTemplate/fitToContent/filter/searchInFields don’t apply to these).
The popupTemplate string itself is rendered as-is (so <b>, <a href="...">, etc. work), but every ${fieldName} value substituted into it is HTML-escaped first — safe even when the data comes from a source you don’t fully control (a public Directus collection, a third-party CSV/GeoJSON URL).
If two overlay entries share the exact same source, the underlying fetch happens only once — each layer’s own filter is applied on top of the shared cached data.
XyzSourceConfig / WmsSourceConfig
interface XyzSourceConfig { type: 'xyz'; url: string; // {z}/{x}/{y}, optionally {s} tileSize?: number; // default: 256 attribution?: string;}
interface WmsSourceConfig { type: 'wms'; url: string; // WMS endpoint base URL layers: string; // GetMap LAYERS param format?: string; // default: 'image/png' version?: string; // default: '1.3.0' transparent?: boolean; // default: true styles?: string; crs?: string; // default: 'EPSG:3857' tileSize?: number; // default: 256 attribution?: string;}Both raster source types render as an independent overlay — a historical map or satellite layer can be toggled on top of any basemap, rather than only being selectable as the basemap. See the “Raster overlay independent of the basemap” example above.
DirectusShorthand (for Map)
interface DirectusShorthand { table: string; geoField?: string; // Field containing GeoJSON geometry queryString?: string; // Directus filter query string name?: string; // Layer display name popup?: string; // Popup HTML template fitToContent?: boolean; style?: object; url?: string; // defaults to PUBLIC_DIRECTUS_URL token?: string; // defaults to PUBLIC_DIRECTUS_TOKEN}