Web Components
The <ml-map> web component is the simplest way to add maps to any HTML page. No JavaScript required for basic usage - just HTML and a YAML configuration file.
Quick Start
Section titled “Quick Start”Get a map on your page in 3 steps.
-
Add the scripts and styles
<head><!-- MapLibre GL CSS --><linkhref="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css"rel="stylesheet"/><!-- Register the web component --><script type="module">import "https://unpkg.com/@maplibre-yaml/core/register";</script></head> -
Create a YAML configuration file
Create
map.yamlin your project:type: mapid: quick-mapconfig:center: [-74.006, 40.7128]zoom: 12mapStyle: "https://demotiles.maplibre.org/style.json"layers:- id: markertype: circlesource:type: geojsondata:type: FeatureCollectionfeatures:- type: Featuregeometry:type: Pointcoordinates: [-74.006, 40.7128]properties:name: "New York"paint:circle-radius: 10circle-color: "#3b82f6" -
Add the map component
<ml-map src="map.yaml" style="width: 100%; height: 400px; display: block;"></ml-map> -
View in browser
Serve your files with any web server (e.g.,
npx serve .) and open in browser!
Installation Options
Section titled “Installation Options”CDN (Recommended for Quick Start)
Section titled “CDN (Recommended for Quick Start)”<script type="module"> import "https://unpkg.com/@maplibre-yaml/core/register";</script>npm (With Build Tools)
Section titled “npm (With Build Tools)”npm install @maplibre-yaml/core maplibre-gl// In your JavaScript entry pointimport "@maplibre-yaml/core/register";import "maplibre-gl/dist/maplibre-gl.css";Configuration Methods
Section titled “Configuration Methods”There are three ways to configure <ml-map>, each suited for different use cases.
Method 1: External YAML File (Recommended)
Section titled “Method 1: External YAML File (Recommended)”The simplest and cleanest approach - keep your configuration in a separate YAML file:
<ml-map src="/maps/my-map.yaml" style="height: 400px; display: block;"></ml-map>my-map.yaml:
type: mapid: my-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: points type: circle source: type: geojson url: "https://example.com/data.geojson" paint: circle-radius: 8 circle-color: "#3b82f6"Why this is best:
- ✅ Clean separation of configuration and markup
- ✅ No JavaScript required
- ✅ YAML indentation is never an issue
- ✅ Easy to edit without touching HTML
- ✅ Files can be validated independently
- ✅ Works in all frameworks
Method 2: Inline YAML (Script Tag)
Section titled “Method 2: Inline YAML (Script Tag)”For self-contained examples or documentation:
<ml-map style="height: 400px; display: block;"> <script type="text/yaml">type: mapid: inline-mapconfig: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: marker type: circle source: type: geojson data: type: FeatureCollection features: - type: Feature geometry: type: Point coordinates: [-74.006, 40.7128] paint: circle-radius: 10 circle-color: "#3b82f6" </script></ml-map>Why this works:
- Browser doesn’t process
<script type="text/yaml">content - Whitespace and indentation are preserved exactly
- Works in React, Vue, Astro, MDX - everywhere!
- Good for examples that need to be self-contained
Method 3: JSON Attribute (Programmatic)
Section titled “Method 3: JSON Attribute (Programmatic)”For dynamic or generated configurations:
<ml-map id="dynamic-map" style="height: 400px; display: block;"></ml-map>
<script type="module"> import "https://unpkg.com/@maplibre-yaml/core/register";
const config = { type: "map", id: "programmatic", config: { center: [-74.006, 40.7128], zoom: 12, mapStyle: "https://demotiles.maplibre.org/style.json", }, layers: [], };
// Set as property (object) document.getElementById("dynamic-map").config = config;
// Or as attribute (JSON string) // document.getElementById("dynamic-map").setAttribute("config", JSON.stringify(config));</script>When to use this:
- Generating config from user input
- Loading config from an API
- Building configs programmatically
- Framework-based dynamic rendering
Component Lifecycle
Section titled “Component Lifecycle”Initialization Flow
Section titled “Initialization Flow”1. Element added to DOM2. connectedCallback() fires3. Config attribute/property read4. Config validated against schema5. MapLibre GL map created6. Layers added7. 'ml-map:load' event dispatchedLifecycle Events
Section titled “Lifecycle Events”const mapEl = document.querySelector("ml-map");
// Map loaded and readymapEl.addEventListener("ml-map:load", (event) => { console.log("Map ready!");});
// Error during initialization or runtimemapEl.addEventListener("ml-map:error", (event) => { console.error("Map error:", event.detail.error);});
// Layer data loadedmapEl.addEventListener("ml-map:layer-loaded", (event) => { const { layerId, featureCount } = event.detail; console.log(`${layerId}: ${featureCount} features`);});
// Layer data loadingmapEl.addEventListener("ml-map:layer-loading", (event) => { console.log(`Loading ${event.detail.layerId}...`);});
// Layer errormapEl.addEventListener("ml-map:layer-error", (event) => { console.error(`${event.detail.layerId} error:`, event.detail.error);});Accessing the MapLibre Instance
Section titled “Accessing the MapLibre Instance”For advanced customization, access the underlying MapLibre GL map:
const mapEl = document.querySelector("ml-map");
mapEl.addEventListener("ml-map:load", () => { // Get the MapLibre GL map instance const map = mapEl.getMap();
// Now you can use any MapLibre GL API map.flyTo({ center: [-122.4194, 37.7749], zoom: 14, duration: 2000, });
// Add custom controls map.addControl(new maplibregl.NavigationControl());
// Add custom layers map.addLayer({ id: "custom-layer", type: "fill", source: "some-source", paint: { "fill-color": "#ff0000" }, });});Controlling Live Data
Section titled “Controlling Live Data”For maps with polling or streaming data:
const mapEl = document.querySelector("ml-map");
mapEl.addEventListener("ml-map:load", () => { // Pause auto-refresh mapEl.pauseRefresh("earthquakes");
// Resume auto-refresh mapEl.resumeRefresh("earthquakes");
// Trigger immediate refresh mapEl.refreshNow("earthquakes").then(() => { console.log("Data refreshed"); });});Complete Example: Earthquake Monitor
Section titled “Complete Example: Earthquake Monitor”This example demonstrates a complete implementation using the src attribute with event handlers for status updates.
index.html:
<!DOCTYPE html><html> <head> <title>Earthquake Monitor</title> <link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet" /> <script type="module"> import "https://unpkg.com/@maplibre-yaml/core/register"; </script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: system-ui, sans-serif; }
.container { display: flex; flex-direction: column; height: 100vh; }
header { padding: 15px 20px; background: #1f2937; color: white; display: flex; justify-content: space-between; align-items: center; }
h1 { font-size: 1.25rem; font-weight: 600; }
.status { font-size: 0.875rem; color: #9ca3af; }
.status.loading { color: #fbbf24; } .status.error { color: #ef4444; } .status.ready { color: #22c55e; }
ml-map { flex: 1; display: block; } </style> </head> <body> <div class="container"> <header> <h1>🌍 Earthquake Monitor</h1> <div class="status" id="status">Initializing...</div> </header>
<ml-map id="earthquake-map" src="earthquake-config.yaml"></ml-map> </div>
<script type="module"> const statusEl = document.getElementById("status"); const mapEl = document.getElementById("earthquake-map");
// Event handlers mapEl.addEventListener("ml-map:loading", (event) => { statusEl.textContent = "Loading configuration..."; statusEl.className = "status loading"; });
mapEl.addEventListener("ml-map:load", () => { statusEl.textContent = "Loading earthquake data..."; statusEl.className = "status loading"; });
mapEl.addEventListener("ml-map:layer-loaded", (event) => { const { featureCount } = event.detail; const time = new Date().toLocaleTimeString(); statusEl.textContent = `${featureCount} earthquakes · ${time}`; statusEl.className = "status ready"; });
mapEl.addEventListener("ml-map:layer-error", (event) => { statusEl.textContent = `Data error: ${event.detail.error.message}`; statusEl.className = "status error"; });
mapEl.addEventListener("ml-map:error", (event) => { statusEl.textContent = `Error: ${event.detail.error?.message || event.detail.errors?.[0]?.message}`; statusEl.className = "status error"; }); </script> </body></html>earthquake-config.yaml:
type: mapid: earthquake-monitorconfig: center: [-120, 40] zoom: 4 mapStyle: "https://demotiles.maplibre.org/style.json"layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson" refreshInterval: 60000 updateStrategy: replace paint: circle-radius: - interpolate - ["linear"] - ["get", "mag"] - 0 - 4 - 5 - 20 circle-color: - interpolate - ["linear"] - ["get", "mag"] - 0 - "#22c55e" - 3 - "#eab308" - 5 - "#ef4444" circle-opacity: 0.8 circle-stroke-width: 1 circle-stroke-color: "#ffffff" interactive: hover: cursor: pointer click: popup: - h3: - property: title - p: - str: "Magnitude: " - property: mag - p: - str: "Depth: " - property: depth - str: " km"Key improvements in this example:
- ✅ No YAML parsing code needed - component handles it
- ✅ Simpler, more readable HTML
- ✅ YAML file is separate and easy to edit
- ✅ Event handlers provide rich status feedback
Multiple Maps on One Page
Section titled “Multiple Maps on One Page”Each <ml-map> is independent:
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; height: 100vh;"> <ml-map id="map-1" style="display: block;"></ml-map> <ml-map id="map-2" style="display: block;"></ml-map></div>
<script type="module"> import "https://unpkg.com/@maplibre-yaml/core/register";
// Configure each map independently document.getElementById("map-1").config = { /* config 1 */ }; document.getElementById("map-2").config = { /* config 2 */ };</script>Framework Compatibility
Section titled “Framework Compatibility”The <ml-map> web component works in any framework:
import "@maplibre-yaml/core/register";
function Map({ config }) { const configJson = JSON.stringify(config);
return ( <ml-map style={{ width: "100%", height: "400px", display: "block" }} config={configJson} /> );}<template> <ml-map :config="configJson" style="width: 100%; height: 400px; display: block;" /></template>
<script setup>import "@maplibre-yaml/core/register";import { computed } from "vue";
const props = defineProps(["config"]);const configJson = computed(() => JSON.stringify(props.config));</script>Svelte
Section titled “Svelte”<script> import '@maplibre-yaml/core/register'; export let config;</script>
<ml-map config={JSON.stringify(config)} style="width: 100%; height: 400px; display: block;" />Troubleshooting
Section titled “Troubleshooting”Component Not Rendering
Section titled “Component Not Rendering”Symptoms: <ml-map> element exists but shows nothing.
Solutions:
-
Check registration
// Verify component is registeredconsole.log(customElements.get("ml-map")); // Should not be undefined -
Check dimensions
ml-map {display: block; /* Required! */width: 100%;height: 400px;} -
Check config is valid JSON
try {JSON.parse(mapEl.getAttribute("config"));} catch (e) {console.error("Invalid JSON config");}
”Invalid map config in attribute” Error
Section titled “”Invalid map config in attribute” Error”Symptoms: Console shows Zod validation errors.
Cause: Configuration doesn’t match schema.
Debug:
mapEl.addEventListener("ml-map:error", (event) => { console.error("Config error:", event.detail.error); // Check which fields are invalid});Common fixes:
- Ensure
type: "map"is present - Ensure
idis a string - Ensure
config.centeris[longitude, latitude] - Ensure
config.mapStyleis a valid URL
Events Not Firing
Section titled “Events Not Firing”Symptoms: Event listeners never trigger.
Solutions:
-
Add listeners before setting config
const mapEl = document.getElementById('my-map');// ✅ Add listener firstmapEl.addEventListener('ml-map:load', () => {});// Then set configmapEl.config = { ... }; -
Check for errors
mapEl.addEventListener("ml-map:error", (event) => {console.error("Error prevented load:", event.detail.error);});
Map Loads but No Layers Visible
Section titled “Map Loads but No Layers Visible”Symptoms: Base map shows, but data layers don’t appear.
Debug steps:
-
Check layer loading events
mapEl.addEventListener("ml-map:layer-loading", (e) =>console.log("Loading:", e.detail.layerId));mapEl.addEventListener("ml-map:layer-loaded", (e) =>console.log("Loaded:", e.detail.layerId, e.detail.featureCount));mapEl.addEventListener("ml-map:layer-error", (e) =>console.error("Error:", e.detail.layerId, e.detail.error)); -
Check network requests - Open browser DevTools → Network tab
-
Verify GeoJSON URL - Open the URL directly in browser
-
Check for CORS issues - Look for CORS errors in console
Component API Reference
Section titled “Component API Reference”Attributes
Section titled “Attributes”| Attribute | Type | Description |
|---|---|---|
src | string | URL to external YAML configuration file |
config | JSON string | Map configuration as stringified JSON |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
config | Object or string | Map configuration object (set/get) |
Methods
Section titled “Methods”| Method | Returns | Description |
|---|---|---|
getMap() | maplibregl.Map | Returns the MapLibre GL map instance |
getRenderer() | MapRenderer | Returns the MapRenderer instance |
pauseRefresh(layerId) | void | Pause polling for a layer |
resumeRefresh(layerId) | void | Resume polling for a layer |
refreshNow(layerId) | Promise<void> | Trigger immediate data refresh |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
ml-map:load | {} | Map loaded and ready |
ml-map:error | { error: Error } | Error occurred |
ml-map:layer-loading | { layerId: string } | Layer data loading |
ml-map:layer-loaded | { layerId: string, featureCount: number } | Layer data loaded |
ml-map:layer-error | { layerId: string, error: Error } | Layer data error |
Next Steps
Section titled “Next Steps”- Vanilla JavaScript Integration - More control with direct API usage
- Astro Integration - For Astro-based sites
- Live Data Guide - Polling and streaming configuration
- API Reference - Complete API documentation