Skip to content

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.

Get a map on your page in 3 steps.

  1. Add the scripts and styles

    <head>
    <!-- MapLibre GL CSS -->
    <link
    href="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>
  2. Create a YAML configuration file

    Create map.yaml in your project:

    type: map
    id: quick-map
    config:
    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]
    properties:
    name: "New York"
    paint:
    circle-radius: 10
    circle-color: "#3b82f6"
  3. Add the map component

    <ml-map src="map.yaml" style="width: 100%; height: 400px; display: block;">
    </ml-map>
  4. View in browser

    Serve your files with any web server (e.g., npx serve .) and open in browser!

<script type="module">
import "https://unpkg.com/@maplibre-yaml/core/register";
</script>
Terminal window
npm install @maplibre-yaml/core maplibre-gl
// In your JavaScript entry point
import "@maplibre-yaml/core/register";
import "maplibre-gl/dist/maplibre-gl.css";

There are three ways to configure <ml-map>, each suited for different use cases.

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: map
id: my-map
config:
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

For self-contained examples or documentation:

<ml-map style="height: 400px; display: block;">
<script type="text/yaml">
type: map
id: inline-map
config:
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

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
1. Element added to DOM
2. connectedCallback() fires
3. Config attribute/property read
4. Config validated against schema
5. MapLibre GL map created
6. Layers added
7. 'ml-map:load' event dispatched
const mapEl = document.querySelector("ml-map");
// Map loaded and ready
mapEl.addEventListener("ml-map:load", (event) => {
console.log("Map ready!");
});
// Error during initialization or runtime
mapEl.addEventListener("ml-map:error", (event) => {
console.error("Map error:", event.detail.error);
});
// Layer data loaded
mapEl.addEventListener("ml-map:layer-loaded", (event) => {
const { layerId, featureCount } = event.detail;
console.log(`${layerId}: ${featureCount} features`);
});
// Layer data loading
mapEl.addEventListener("ml-map:layer-loading", (event) => {
console.log(`Loading ${event.detail.layerId}...`);
});
// Layer error
mapEl.addEventListener("ml-map:layer-error", (event) => {
console.error(`${event.detail.layerId} error:`, event.detail.error);
});

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" },
});
});

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");
});
});

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: map
id: earthquake-monitor
config:
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

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>

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>
<script>
import '@maplibre-yaml/core/register';
export let config;
</script>
<ml-map config={JSON.stringify(config)} style="width: 100%; height: 400px; display: block;" />

Symptoms: <ml-map> element exists but shows nothing.

Solutions:

  1. Check registration

    // Verify component is registered
    console.log(customElements.get("ml-map")); // Should not be undefined
  2. Check dimensions

    ml-map {
    display: block; /* Required! */
    width: 100%;
    height: 400px;
    }
  3. 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 id is a string
  • Ensure config.center is [longitude, latitude]
  • Ensure config.mapStyle is a valid URL

Symptoms: Event listeners never trigger.

Solutions:

  1. Add listeners before setting config

    const mapEl = document.getElementById('my-map');
    // ✅ Add listener first
    mapEl.addEventListener('ml-map:load', () => {});
    // Then set config
    mapEl.config = { ... };
  2. Check for errors

    mapEl.addEventListener("ml-map:error", (event) => {
    console.error("Error prevented load:", event.detail.error);
    });

Symptoms: Base map shows, but data layers don’t appear.

Debug steps:

  1. 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)
    );
  2. Check network requests - Open browser DevTools → Network tab

  3. Verify GeoJSON URL - Open the URL directly in browser

  4. Check for CORS issues - Look for CORS errors in console

AttributeTypeDescription
srcstringURL to external YAML configuration file
configJSON stringMap configuration as stringified JSON
PropertyTypeDescription
configObject or stringMap configuration object (set/get)
MethodReturnsDescription
getMap()maplibregl.MapReturns the MapLibre GL map instance
getRenderer()MapRendererReturns the MapRenderer instance
pauseRefresh(layerId)voidPause polling for a layer
resumeRefresh(layerId)voidResume polling for a layer
refreshNow(layerId)Promise<void>Trigger immediate data refresh
EventDetailDescription
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