Interactivity
Interactivity in maplibre-yaml is primarily configured through:
- Map Controls - Navigation, scale, geolocation, fullscreen
- Legends - Interactive layer information
- Layer Interactions - Click, hover, and pointer events (configured at layer level)
Map Controls
Section titled “Map Controls”Map controls provide standard UI elements for map interaction. See Map Configuration for detailed control configuration.
Available Controls
Section titled “Available Controls”controls: navigation: true # Zoom and rotation controls geolocate: true # User geolocation button scale: true # Distance scale fullscreen: true # Fullscreen toggle attribution: true # Attribution textControl Positioning
Section titled “Control Positioning”Each control can be positioned independently:
controls: navigation: enabled: true position: top-left scale: enabled: true position: bottom-rightAvailable positions:
top-lefttop-rightbottom-leftbottom-right
Attribution
Section titled “Attribution”attribution renders a real attribution control and additionally accepts
MapLibre’s own options:
controls: attribution: position: bottom-right # default compact: true # collapse behind an (i) button customAttribution: "© Example Data"Configuring it suppresses MapLibre’s built-in attribution so only one control renders. Leave it unset and the built-in one still appears — attribution is a licensing obligation for most tile providers, so it is never removed silently.
Interactive Legends
Section titled “Interactive Legends”Legends display layer information and can be positioned anywhere on the map.
Basic Legend
Section titled “Basic Legend”legend: title: "Map Features" position: top-right collapsed: falseCustom Legend Items
Section titled “Custom Legend Items”legend: title: "Data Categories" position: top-left items: - color: "#e74c3c" label: "High Priority" shape: circle - color: "#f39c12" label: "Medium Priority" shape: circle - color: "#2ecc71" label: "Low Priority" shape: circleLegend Item Shapes
Section titled “Legend Item Shapes”circle- Circular symbolsquare- Square symbolline- Line symbolicon- Custom icon (requiresiconproperty)
legend: title: "Transportation" items: - color: "#3b82f6" label: "Highway" shape: line - color: "#10b981" label: "Local Road" shape: line - color: "#000000" label: "Restaurant" shape: icon icon: "restaurant"Map Interaction Settings
Section titled “Map Interaction Settings”Control which user interactions are enabled.
Disable All Interactions
Section titled “Disable All Interactions”Create a static presentation map:
config: center: [-74.006, 40.7128] zoom: 12 interactive: false mapStyle: "..."Selective Interactions
Section titled “Selective Interactions”Enable specific interactions:
config: center: [-74.006, 40.7128] zoom: 12 scrollZoom: true dragPan: true dragRotate: false # Disable rotation touchPitch: false # Disable pitch on mobile doubleClickZoom: false # Disable double-click zoom mapStyle: "..."Interaction Properties
Section titled “Interaction Properties”| Property | Type | Default | Description |
|---|---|---|---|
interactive | boolean | true | Enable all interactions |
scrollZoom | boolean | true | Scroll to zoom |
boxZoom | boolean | true | Shift+drag box zoom |
dragRotate | boolean | true | Right-drag to rotate |
dragPan | boolean | true | Drag to pan |
keyboard | boolean | true | Keyboard shortcuts |
doubleClickZoom | boolean | true | Double-click zoom |
touchZoomRotate | boolean | true | Touch zoom/rotate |
touchPitch | boolean | true | Two-finger pitch |
Layer Interactivity
Section titled “Layer Interactivity”Layers declare interactivity through an interactive: block. These are real
schema fields handled by the renderer — not something you wire up yourself.
- id: stations type: circle source: type: geojson url: "https://example.com/stations.geojson" generateId: true paint: circle-radius: 8 circle-color: "#3b82f6" interactive: hover: cursor: pointer highlight: true click: popup: - h3: [{ property: name }] - p: [{ property: description }] flyTo: zoom: 14 duration: 600| Property | Type | Description |
|---|---|---|
cursor | string | CSS cursor while the pointer is over a feature |
highlight | boolean | Recolour the hovered feature |
highlight: true wraps the layer’s primary colour in a feature-state expression
for you, so no paint authoring is required. Two things follow from how
feature-state works:
- Features need ids. Set
generateId: trueon the source, orpromoteIdto use a property as the id. If neither is set,highlightenablesgenerateIdand warns — but generated ids are not stable across data refreshes, sopromoteIdis the durable choice. - Already using an expression for that colour? Then
highlightleaves your paint alone and warns, rather than overwriting your data-driven styling. Reference["feature-state", "hover"]yourself, as in Custom highlight styling below.
Because feature-state is per source, two layers sharing one source share highlight state.
| Property | Type | Description |
|---|---|---|
popup | array | Popup content blocks (see Popups) |
flyTo | object | Animate the camera to the clicked feature |
flyTo accepts center, zoom, and duration. center defaults to the
clicked point, and zoom/duration fall through to MapLibre’s defaults when
omitted:
interactive: click: flyTo: zoom: 14 # optional duration: 600 # optional, millisecondsWhen a layer configures both, the popup opens first and then travels with the camera.
Deprecated: action
Section titled “Deprecated: action”click.action, mouseenter.action, and mouseleave.action are accepted by the
schema but have never been dispatched at runtime. They emit a deprecation
warning and will be removed in v2. Listen for the corresponding event
instead:
# Deprecated — never firedinteractive: click: action: openDetails// Use the event insteadmap.addEventListener("ml-map:layer-click", (e) => { const { layerId, feature } = e.detail;});Deprecation warnings are exempt from mlym validate’s CI strict mode for one
minor release, so upgrading will not fail your build. Opt in early with
mlym validate --strict-deprecations.
Custom highlight styling
Section titled “Custom highlight styling”To control the highlight colour yourself, author the paint expression directly
and leave highlight off:
- id: interactive-fill type: fill source: type: geojson url: "https://example.com/regions.geojson" generateId: true paint: fill-color: - case - ["boolean", ["feature-state", "hover"], false] - "#3b82f6" # Blue when hovered - "#e5e7eb" # Gray otherwise fill-opacity: - case - ["boolean", ["feature-state", "hover"], false] - 0.8 - 0.5 interactive: hover: cursor: pointerKeyboard Shortcuts
Section titled “Keyboard Shortcuts”When keyboard interaction is enabled (keyboard: true), the following shortcuts are available:
| Keys | Action |
|---|---|
+ / = | Zoom in |
- | Zoom out |
| Arrow keys | Pan map |
| Shift + Arrow keys | Rotate map |
Shift + + / - | Increase/decrease pitch |
Disable Keyboard
Section titled “Disable Keyboard”config: center: [0, 0] zoom: 2 keyboard: false mapStyle: "..."Touch Interactions
Section titled “Touch Interactions”Control touch gestures on mobile devices.
Enable All Touch
Section titled “Enable All Touch”config: center: [0, 0] zoom: 2 touchZoomRotate: true touchPitch: true mapStyle: "..."Disable Pitch on Touch
Section titled “Disable Pitch on Touch”Prevent accidental camera tilt:
config: center: [0, 0] zoom: 2 touchZoomRotate: true touchPitch: false mapStyle: "..."URL Hash Syncing
Section titled “URL Hash Syncing”Sync map state (center, zoom, bearing, pitch) with the URL hash for bookmarkable views.
config: center: [-74.006, 40.7128] zoom: 12 hash: true # Enable URL hash syncing mapStyle: "..."When enabled, the URL will update as users interact with the map:
https://example.com/map#12/40.7128/-74.006Cursor Styling
Section titled “Cursor Styling”The map automatically changes the cursor to indicate interactivity:
- Default cursor over the map
- Pointer cursor over interactive features
- Grab cursor when dragging
Complete Examples
Section titled “Complete Examples”Fully Interactive Map
Section titled “Fully Interactive Map”- type: map id: interactive-map config: center: [-74.006, 40.7128] zoom: 12 hash: true mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: buildings type: fill source: type: geojson url: "https://example.com/buildings.geojson" paint: fill-color: - case - ["boolean", ["feature-state", "hover"], false] - "#3b82f6" - "#cbd5e1" fill-opacity: 0.7 controls: navigation: true geolocate: true scale: true fullscreen: true legend: title: "Buildings" position: top-right items: - color: "#cbd5e1" label: "Building" shape: square - color: "#3b82f6" label: "Hovered" shape: squareLimited Interaction Map
Section titled “Limited Interaction Map”- type: map id: limited-map config: center: [0, 20] zoom: 2 scrollZoom: true dragPan: true dragRotate: false touchPitch: false doubleClickZoom: false keyboard: false minZoom: 1 maxZoom: 8 mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: data-layer type: circle source: type: geojson url: "https://example.com/data.geojson" paint: circle-radius: 6 circle-color: "#3b82f6" controls: navigation: true scale: true legend: title: "Data Points" position: top-leftStatic Presentation Map
Section titled “Static Presentation Map”- type: map id: static-map style: "height: 500px;" config: center: [-74.006, 40.7128] zoom: 12 pitch: 45 bearing: -30 interactive: false attributionControl: false mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: buildings type: fill-extrusion source: type: vector url: "https://demotiles.maplibre.org/tiles/tiles.json" source-layer: building paint: fill-extrusion-color: "#aaa" fill-extrusion-height: ["get", "height"] fill-extrusion-opacity: 0.8 legend: title: "3D Buildings" position: bottom-right items: - color: "#aaa" label: "Building" shape: squareTouch-Optimized Mobile Map
Section titled “Touch-Optimized Mobile Map”- type: map id: mobile-map config: center: [-74.006, 40.7128] zoom: 12 touchZoomRotate: true touchPitch: false # Disable pitch to prevent accidental tilts dragRotate: false # Disable rotation keyboard: false # Not needed on mobile mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: points type: circle source: type: geojson url: "https://example.com/points.geojson" paint: circle-radius: 12 # Larger for touch targets circle-color: "#3b82f6" circle-stroke-width: 2 circle-stroke-color: "#ffffff" controls: navigation: enabled: true position: top-right geolocate: enabled: true position: top-right scale: enabled: true position: bottom-left legend: title: "Locations" position: top-left collapsed: true # Start collapsed on mobileBest Practices
Section titled “Best Practices”Performance
Section titled “Performance”- Limit interactive features: Too many interactive features can impact performance
- Use feature-state: Prefer feature-state for hover/select styling over re-rendering
- Throttle updates: Limit update frequency for real-time data sources
User Experience
Section titled “User Experience”- Provide visual feedback: Use hover states to indicate clickable features
- Clear controls: Position controls logically and consistently
- Mobile-first: Disable complex gestures like pitch on mobile
- Accessibility: Include keyboard navigation and clear visual indicators
Map Constraints
Section titled “Map Constraints”- Set zoom limits: Use
minZoom/maxZoomto keep users in relevant area - Geographic bounds: Use
maxBoundsto restrict panning - Disable unnecessary interactions: Remove interactions that don’t serve your use case
TypeScript Types
Section titled “TypeScript Types”import { type ControlsConfig, type LegendConfig, type MapConfig} from '@maplibre-yaml/core/schemas';
const controls: ControlsConfig = { navigation: { enabled: true, position: "top-right" }, scale: true, geolocate: true};
const legend: LegendConfig = { title: "Features", position: "top-left", collapsed: false, items: [ { color: "#ff0000", label: "High", shape: "circle" }, { color: "#00ff00", label: "Low", shape: "circle" } ]};
const interactiveConfig: MapConfig = { center: [-74.006, 40.7128], zoom: 12, mapStyle: "https://demotiles.maplibre.org/style.json", interactive: true, scrollZoom: true, dragPan: true, hash: true};