Skip to content
Development
Skill

/web-maps-leaflet

Leaflet interactive maps. Use when building a 2D map with tile layers, markers, popups, GeoJSON, layer control, custom controls or marker clustering.

From plugin
agents-inc-skills
24200 skills
Install
$ npx -y skills add agents-inc/skills --skill web-maps-leaflet --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/web-maps-leaflet

Context preview

The summary Claude sees to decide when to auto-load this skill.

Leaflet interactive maps. Use when building a 2D map with tile layers, markers, popups, GeoJSON, layer control, custom controls or marker clustering.

SKILL.md

web-maps-leaflet.SKILL.md
name: web-maps-leaflet
description: Leaflet interactive maps. Use when building a 2D map with tile layers, markers, popups, GeoJSON, layer control, custom controls or marker clustering.

Leaflet Interactive Map Patterns

> **Quick Guide:** Leaflet is a small 2D mapping library: `L.map` initializes against a DOM element, > `L.tileLayer` supplies the base map, and everything drawn on top — markers, popups, GeoJSON, > controls — is a layer added to and removed from the map independently. `L.geoJSON` does most of > the data work through its `pointToLayer`, `onEachFeature`, `style` and `filter` callbacks. Marker > count is the decision that shapes the rest: past a hundred, DOM markers stop scaling and the work > moves to clustering or the canvas renderer. **Current: v1.9.4**, with types in `@types/leaflet`.

**Detailed Resources:**

  • [examples/core.md](examples/core.md) — map setup, tile providers, markers and icons, GeoJSON, layer groups and control, events
  • [examples/advanced.md](examples/advanced.md) — custom controls, clustering, TypeScript, canvas rendering, bounds and viewport
  • [reference.md](reference.md) — `L.map` methods, `L.geoJSON` and cluster options, event table, install checklist

---

<critical_requirements>

Before writing Leaflet code

**Call `map.remove()` when the map goes away.** It is the one call that tears down the resize observers, animation frames and DOM listeners Leaflet attached; without it a re-mount on the same element throws "Map container is already initialized".

**Give every tile layer an `attribution`.** OpenStreetMap and most other providers require it in their terms, and there is no default.

**Move past DOM markers at around a hundred points** — `L.markerClusterGroup`, or `L.circleMarker` on the canvas renderer. Each `L.marker` is an element in the document, and the page slows in proportion.

</critical_requirements>

---

**Auto-detection:** Leaflet, `L.map`, `L.tileLayer`, `L.marker`, `L.popup`, `L.geoJSON`, `L.control.layers`, `L.layerGroup`, `L.featureGroup`, `L.icon`, `L.divIcon`, `L.circleMarker`, `L.polyline`, `L.polygon`, `L.Control.extend`, `L.DomUtil`, `L.DomEvent`, markerClusterGroup, leaflet.markercluster, `@types/leaflet`, leaflet.css, addTo(map), bindPopup, bindTooltip, onEachFeature, pointToLayer, invalidateSize, flyTo, fitBounds, latLngBounds

**Applies to:**

  • Interactive 2D maps with markers, popups, tooltips and overlays
  • GeoJSON points, lines and polygons, styled and filtered from their own properties
  • Base-layer switching and overlay toggling through a layer control
  • Custom controls built on `L.Control.extend`
  • Large marker datasets, through clustering or the canvas renderer
  • Map, marker and layer events, and camera movement

**Handled elsewhere:**

  • Where the tiles come from — Leaflet renders any XYZ raster endpoint, and choosing a provider and

meeting its terms is a separate decision

  • Where the GeoJSON comes from — the layer takes an object, and fetching, caching and paging it are

not the map's concern

  • How markers, popups and controls look — the map hands you class names and containers, and the CSS

inside them is settled by whatever owns styling

  • 3D terrain, globe projection and GPU-rendered vector tiles — this is a 2D raster library with an

SVG or canvas vector layer over it

---

<philosophy>

**Everything on the map is a layer.** Tiles, markers, GeoJSON, even controls — each is added and removed independently, which is why toggling a dataset is `map.removeLayer(group)` rather than a rebuild.

**The core is deliberately small** (~42KB gzipped) and covers the common map. Clustering, heatmaps, drawing and vector tiles are plugins, and a plugin is how the library expects those needs to be met.

**Methods return `this`**, so setup reads as a chain: `L.marker(pos).addTo(map).bindPopup(html)`.

**Interaction is events.** Maps, markers and layers all emit; `.on()` subscribes and `.off()` unsubscribes, and `map.off()` with no arguments is part of teardown.

</philosophy>

---

<decision_framework>

Marker strategy, by count

< 100     → L.marker with L.icon or L.divIcon
100 – 10K → L.markerClusterGroup
10K – 50K → L.markerClusterGroup with chunkedLoading, and L.circleMarker rather than L.marker
50K+      → canvas rendering, or pre-tiled vector data

Which layer type

One coordinate               → L.marker (with an icon) or L.circleMarker (for data viz)
A path                       → L.polyline
An area                      → L.polygon, or L.circle for a radius in metres
A GeoJSON dataset            → L.geoJSON, which handles every geometry type
A group you need to toggle   → L.layerGroup, or L.featureGroup where you need getBounds()/bindPopup()

Which icon

The default pin          → L.marker() with no icon option
A custom image           → L.icon({ iconUrl, iconSize, iconAnchor })
Several image variants   → L.Icon.extend({ options }), then construct per variant
HTML or CSS content      → L.divIcon({ html, className, iconSize })
A data point, many of    → L.circleMarker — a vector shape, not a DOM element

</decision_framework>

---

<patterns>

Core patterns

Pattern 1: Map initialization and tile layers

Target a DOM element, set the view, add a base layer with its attribution.

import L from "leaflet";
import "leaflet/dist/leaflet.css";

const map = L.map("map").setView([51.505, -0.09], 13);

L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
  maxZoom: 19,
  attribution:
    '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);

The CSS import is not optional — without it controls, popups and markers render unpositioned.

Full code: [examples/core.md](examples/core.md)

---

Pattern 2: Markers, popups and tooltips

const marker = L.marker([51.5, -0.09]).addTo(map);
marker.bindPopup("<b>Hello</b><br>I am a popup.");
marker.bindTooltip("Hover text", { direction: "top" });
Read more
Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin

Other skills on agents-inc-skills.