@maplibre-yaml/astro
v0.1.2
Published
Astro components for maplibre-yaml
Maintainers
Readme
@maplibre-yaml/astro
Astro components for creating interactive maps and scrollytelling experiences using YAML configuration.
Installation
npm install @maplibre-yaml/astro @maplibre-yaml/core maplibre-glQuick Start
Basic Map
---
import { Map } from '@maplibre-yaml/astro';
---
<Map src="/configs/my-map.yaml" height="400px" />Full-Page Map with Controls
---
import { FullPageMap } from '@maplibre-yaml/astro';
---
<FullPageMap
src="/configs/dashboard.yaml"
showControls
showLegend
legendPosition="bottom-left"
/>Scrollytelling Story
---
import { Scrollytelling } from '@maplibre-yaml/astro';
---
<Scrollytelling src="/stories/earthquake-history.yaml" />Components
Map
Basic map component for embedding maps in your pages.
Props:
src?: string- Path to YAML config file (in/public)config?: MapBlock- Pre-loaded configuration objectheight?: string- Map height (default: "400px")class?: string- Additional CSS classesstyle?: string- Additional inline styles
Example:
---
import { Map, loadMapConfig } from '@maplibre-yaml/astro';
const config = await loadMapConfig('./src/configs/map.yaml');
---
<Map config={config} height="600px" />FullPageMap
Full-viewport map with built-in controls and optional legend.
Props:
src?: string- Path to YAML config fileconfig?: MapBlock- Pre-loaded configurationshowControls?: boolean- Show zoom/reset controls (default: true)showLegend?: boolean- Show auto-generated legend (default: false)legendPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'- Legend positionclass?: string- Additional CSS classesstyle?: string- Additional inline styles
Scrollytelling
Immersive narrative experiences with scroll-driven map transitions.
Props:
src?: string- Path to YAML story configconfig?: ScrollytellingBlock- Pre-loaded story configurationclass?: string- Additional CSS classesdebug?: boolean- Show debug outlines
Chapter
Individual chapter component (typically used internally by Scrollytelling).
Props:
id: string- Unique chapter identifiertitle: string- Chapter titledescription?: string- HTML descriptionimage?: string- Image URLvideo?: string- Video URLalignment?: 'left' | 'right' | 'center' | 'full'- Content alignmenthidden?: boolean- Hide content for map-only chapterstheme?: 'light' | 'dark'- Visual themeisActive?: boolean- Active state
Global Configuration
Define site-wide map defaults (style, center, zoom) that are inherited by all maps. This avoids repeating the same base configuration on every page.
1. Create a global config YAML file
# src/config/maps.yaml
title: "My Map App"
defaultMapStyle: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
defaultCenter: [-73.985, 40.674]
defaultZoom: 14
theme: light2. Create a shared loader module
// src/lib/map-config.ts
import { loadGlobalMapConfig } from "@maplibre-yaml/astro";
export const globalMapConfig = await loadGlobalMapConfig(
"./src/config/maps.yaml"
);3. Use in pages with map builders
---
import { Map, buildPointMapConfig } from "@maplibre-yaml/astro";
import { globalMapConfig } from "../lib/map-config";
// mapStyle, center, and zoom are inherited from global config
const mapConfig = buildPointMapConfig(
{
location: {
coordinates: [-73.983, 40.676],
name: "My Location",
},
},
globalMapConfig,
);
---
<Map config={mapConfig} height="400px" />All map builders (buildPointMapConfig, buildPolygonMapConfig, buildRouteMapConfig, buildMultiPointMapConfig) accept globalConfig as their second argument.
Adding Geographic Data to Existing Collections
You can add map support to any Astro content collection by importing the geographic schemas directly. This is useful when your collection has its own schema and you just want to add optional location data.
Schema setup
// src/content/config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
import {
LocationPointSchema,
RegionPolygonSchema,
RouteLineSchema,
} from "@maplibre-yaml/astro";
const projects = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
status: z.string(),
description: z.string(),
// Add optional geographic data for any geometry type
location: LocationPointSchema.optional(), // point
region: RegionPolygonSchema.optional(), // polygon
route: RouteLineSchema.optional(), // line
}),
});Frontmatter examples
Point (simplest — just coordinates):
---
title: "New Library Branch"
status: "In Progress"
description: "Construction of new public library"
location:
coordinates: [-73.983, 40.676]
name: "487 4th Avenue"
description: "Proposed 3-story library building"
zoom: 16
---Polygon (area/region):
---
title: "Park Renovation"
status: "Planning"
description: "Remediation and renovation of Greene Park"
region:
name: "Greene Park"
description: "Park boundary"
coordinates:
- - [-73.9848, 40.6748]
- [-73.9833, 40.6748]
- [-73.9833, 40.6738]
- [-73.9848, 40.6738]
- [-73.9848, 40.6748]
fillColor: "#2ecc71"
fillOpacity: 0.35
---Line (route/path):
---
title: "Canal Stormwater Management"
status: "Active"
description: "Stormwater infrastructure along the canal"
route:
name: "Canal Path"
description: "Infrastructure route"
coordinates:
- [-73.9890, 40.6790]
- [-73.9870, 40.6760]
- [-73.9855, 40.6730]
- [-73.9840, 40.6700]
color: "#3498db"
width: 4
---Dynamic map per collection item
---
import {
Map,
buildPointMapConfig,
buildPolygonMapConfig,
buildRouteMapConfig,
} from "@maplibre-yaml/astro";
import type { MapBlock } from "@maplibre-yaml/core";
import { globalMapConfig } from "../lib/map-config";
// Assuming `entry` is a collection item with optional geo fields
const { entry } = Astro.props;
const data = entry.data;
let mapConfig: MapBlock;
if (data.region) {
mapConfig = buildPolygonMapConfig({ region: data.region }, globalMapConfig);
} else if (data.route) {
mapConfig = buildRouteMapConfig({ route: data.route }, globalMapConfig);
} else if (data.location) {
mapConfig = buildPointMapConfig({ location: data.location }, globalMapConfig);
} else {
// Fallback: default area map from global config
mapConfig = buildPointMapConfig(
{
location: {
coordinates: globalMapConfig.defaultCenter!,
name: data.title,
},
},
globalMapConfig,
);
}
---
<Map config={mapConfig} height="300px" />Items without any geographic data gracefully fall back to the global default center.
Important Notes
Do NOT import
@maplibre-yaml/core/registerin Astro frontmatter. The register module containsclass MLMap extends HTMLElement, which crashes during Astro's server-side rendering withReferenceError: HTMLElement is not defined. The@maplibre-yaml/astrocomponents handle registration automatically via a client-side<script>tag. If you are writing a custom component using<ml-map>directly, place the import in a<script>tag (notis:inline), never in the frontmatter---block.
Do NOT use
client:onlyor other client directives on components from@maplibre-yaml/astro. They are Astro components (not React/Vue/Svelte), so client directives are invalid and will cause errors.
The
srcprop is a URL path, not a filesystem path. Files in/publicare served from the root, sopublic/configs/map.yamlshould be referenced assrc="/configs/map.yaml".
YAML Configuration
Map
type: map
id: my-map
config:
center: [-122.4, 37.8]
zoom: 12
mapStyle: "https://demotiles.maplibre.org/style.json"
layers:
- id: points-layer
type: circle
source:
type: geojson
url: "/data/points.geojson"
paint:
circle-radius: 8
circle-color: "#3b82f6"Scrollytelling
type: scrollytelling
id: my-story
config:
center: [0, 0]
zoom: 2
mapStyle: "https://demotiles.maplibre.org/style.json"
chapters:
- id: intro
title: "Introduction"
description: "<p>Welcome to the story.</p>"
location:
center: [0, 0]
zoom: 2
alignment: center
- id: detail
title: "The Details"
description: "<p>Here's what happened.</p>"
image: "/images/detail.jpg"
location:
center: [10, 10]
zoom: 8
pitch: 45
bearing: 30
alignment: left
onChapterEnter:
- action: setPaintProperty
layer: data-layer
property: circle-color
value: "#ff0000"
onChapterExit:
- action: setFilter
layer: data-layer
filter: nullUtilities
YAML Loaders
import {
loadMapConfig,
loadScrollytellingConfig,
loadYAML,
loadFromGlob
} from '@maplibre-yaml/astro';
// Load and validate a map config
const map = await loadMapConfig('./src/configs/map.yaml');
// Load and validate a scrollytelling config
const story = await loadScrollytellingConfig('./src/stories/earthquake.yaml');
// Load raw YAML
const raw = await loadYAML('./src/configs/custom.yaml');
// Load multiple files via glob
const maps = await loadFromGlob('./src/configs/*.yaml');Content Collections
Use with Astro Content Collections for type-safe YAML management:
// src/content/config.ts
import { defineCollection } from 'astro:content';
import { getMapSchema, getScrollytellingSchema } from '@maplibre-yaml/astro';
export const collections = {
maps: defineCollection({
type: 'data',
schema: getMapSchema()
}),
stories: defineCollection({
type: 'data',
schema: getScrollytellingSchema()
})
};Collection Items with Geographic Data
Pre-built schemas for adding location data to content collections (blog posts, articles, etc.):
// src/content/config.ts
import { defineCollection } from 'astro:content';
import {
getCollectionItemWithLocationSchema,
getCollectionItemWithLocationsSchema,
getCollectionItemWithRegionSchema,
getCollectionItemWithRouteSchema,
getCollectionItemWithGeoSchema,
} from '@maplibre-yaml/astro';
export const collections = {
// Posts with a single location
posts: defineCollection({
type: 'content',
schema: getCollectionItemWithLocationSchema()
}),
// Travel posts with multiple locations
travel: defineCollection({
type: 'content',
schema: getCollectionItemWithLocationsSchema()
}),
// Neighborhood guides with regions
neighborhoods: defineCollection({
type: 'content',
schema: getCollectionItemWithRegionSchema()
}),
// Hiking guides with routes
trails: defineCollection({
type: 'content',
schema: getCollectionItemWithRouteSchema()
}),
// Mixed geographic content
adventures: defineCollection({
type: 'content',
schema: getCollectionItemWithGeoSchema({
author: z.string(),
category: z.enum(['travel', 'hiking', 'city-guide'])
})
})
};Example frontmatter for a post with location:
---
title: "My Trip to Paris"
pubDate: 2024-03-15
location:
coordinates: [2.3522, 48.8566]
name: "Paris, France"
zoom: 12
---Map Builders
Generate map configurations from collection item geographic data. All builders accept an optional globalConfig as a second argument for inheriting defaults:
import {
buildPointMapConfig,
buildMultiPointMapConfig,
buildPolygonMapConfig,
buildRouteMapConfig,
calculateCenter,
calculateBounds,
loadGlobalMapConfig,
} from '@maplibre-yaml/astro';
const globalConfig = await loadGlobalMapConfig('./src/config/maps.yaml');
// Build a map config from a single location (inherits mapStyle from global)
const mapConfig = buildPointMapConfig(
{ location: post.data.location },
globalConfig,
);
// Build from multiple locations
const multiMap = buildMultiPointMapConfig(
{ locations: travel.data.locations },
globalConfig,
);
// Build from a polygon region
const regionMap = buildPolygonMapConfig(
{ region: guide.data.region },
globalConfig,
);
// Build from a route
const routeMap = buildRouteMapConfig(
{ route: trail.data.route },
globalConfig,
);Custom Schemas
Extend built-in schemas with custom metadata:
import { extendSchema, getMapSchema } from '@maplibre-yaml/astro';
import { z } from 'zod';
const customMapSchema = extendSchema(getMapSchema(), {
author: z.string(),
publishDate: z.date(),
tags: z.array(z.string())
});Error Handling
import { loadMapConfig, YAMLLoadError } from '@maplibre-yaml/astro';
try {
const config = await loadMapConfig('./src/configs/map.yaml');
} catch (error) {
if (error instanceof YAMLLoadError) {
console.error('Validation errors:', error.errors);
console.error('File:', error.filePath);
}
}TypeScript Support
Full TypeScript support with exported types:
import type {
MapProps,
FullPageMapProps,
ScrollytellingProps,
ChapterProps,
LocationPoint,
RegionPolygon,
RouteLine,
PointMapOptions,
MultiPointMapOptions,
PolygonMapOptions,
RouteMapOptions,
} from '@maplibre-yaml/astro';License
MIT
Related Packages
@maplibre-yaml/core- Core library: schemas, parser, renderer@maplibre-yaml/cli- CLI for validation, preview, scaffolding
