| name | preview-leaflet |
| description | Create interactive maps with markers, routes, and geographic data using Leaflet |
| user-invocable | true |
| commands | ["preview-leaflet"] |
Preview Leaflet Skill
Interactive map visualization viewer using Leaflet library for geographic data, markers, routes, and custom overlays.
Agent Usage
When the user asks to create a map visualization, write the Leaflet code and pipe it to the script. Use the Bash tool to execute this skill's run.sh script:
cat route.js | ./run.sh
./run.sh city-map.leaflet
The script handles all HTML generation and automatically opens the result in the browser. Do NOT open the file manually to avoid duplicate tabs.
Usage
/preview-leaflet city-map.leaflet
/preview-leaflet route.map
cat route.js | /preview-leaflet
echo "const map = L.map('map').setView([51.505, -0.09], 13);" | /preview-leaflet
Best Practice: For temporary or generated maps, prefer piping over creating temporary files. This avoids cluttering your filesystem and the content is automatically cleaned up.
Options
The script works with sensible defaults but supports these flags for flexibility:
-o, --output PATH - Custom output path
--no-browser - Skip browser, output file path only
Features
- Interactive maps with pan and zoom
- Markers and popups for locations
- Routes and paths with polylines
- Shapes and areas with polygons and circles
- Multiple tile layers (OpenStreetMap, CartoDB, etc.)
- Tooltips on hover
- Fit bounds to show all points
- Reset view button
- Code viewer toggle
- Responsive design adapts to window size
When to Use This Skill
Use this skill when the user wants to:
- Visualize geographic data on maps
- Plot locations with markers
- Draw routes between points
- Show service areas or regions
- Create interactive location-based visualizations
Leaflet Code Requirements
Your code should:
- Initialize the map with
L.map('map').setView([lat, lng], zoom)
- Add a tile layer (provides map imagery)
- Add markers, paths, or other features
Execution Context
Your code runs with:
- Leaflet v1.9.4 available as
L
- Map container
#map sized and ready in DOM
Complete Example - Simple Map with Marker
const map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
L.marker([51.505, -0.09]).addTo(map).bindPopup('Hello World!').openPopup();
Complete Example - Route with Multiple Points
const map = L.map('map').setView([40.7128, -74.006], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
const route = [
[40.7128, -74.006],
[40.758, -73.9855],
[40.7614, -73.9776],
];
L.polyline(route, {
color: 'red',
weight: 3,
}).addTo(map);
route.forEach((point, i) => {
L.marker(point)
.addTo(map)
.bindPopup(`Stop ${i + 1}`);
});
map.fitBounds(L.latLngBounds(route), { padding: [50, 50] });
Common Patterns
Markers
L.marker([51.5, -0.09]).addTo(map);
L.marker([51.5, -0.09]).addTo(map).bindPopup('Location name');
L.circleMarker([51.5, -0.09], {
radius: 8,
fillColor: '#3498db',
color: 'white',
weight: 2,
fillOpacity: 0.9,
}).addTo(map);
Lines and Shapes
L.polyline(
[
[51.5, -0.09],
[51.51, -0.08],
[51.52, -0.07],
],
{
color: 'red',
weight: 3,
}
).addTo(map);
L.polygon(
[
[51.5, -0.09],
[51.51, -0.08],
[51.52, -0.09],
],
{
color: 'blue',
fillOpacity: 0.5,
}
).addTo(map);
L.circle([51.5, -0.09], {
radius: 500,
color: 'red',
fillOpacity: 0.5,
}).addTo(map);
Popups and Tooltips
marker.bindPopup(`
<div>
<h3>Location Name</h3>
<p>Description here</p>
</div>
`);
marker.bindTooltip('Tooltip text', {
permanent: false,
direction: 'top',
});
marker.bindTooltip('Always visible', {
permanent: true,
direction: 'right',
});
View Control
map.setView([51.5, -0.09], 13);
map.flyTo([51.5, -0.09], 13, {
duration: 2,
});
const bounds = L.latLngBounds([
[51.49, -0.1],
[51.51, -0.08],
]);
map.fitBounds(bounds, {
padding: [50, 50],
});
Tile Layers (Map Styles)
OpenStreetMap (free, default)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 18,
}).addTo(map);
CartoDB Light (clean, minimal)
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap, © CartoDB',
maxZoom: 18,
}).addTo(map);
CartoDB Dark
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap, © CartoDB',
maxZoom: 18,
}).addTo(map);
Complete Example - Multiple Locations
const map = L.map('map').setView([37.7749, -122.4194], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(map);
const locations = [
{ name: 'San Francisco', coords: [37.7749, -122.4194], type: 'city' },
{ name: 'Oakland', coords: [37.8044, -122.2712], type: 'city' },
{ name: 'Berkeley', coords: [37.8715, -122.273], type: 'city' },
];
locations.forEach((loc) => {
const color = loc.type === 'city' ? 'blue' : 'red';
L.circleMarker(loc.coords, {
radius: 8,
fillColor: color,
color: 'white',
weight: 2,
fillOpacity: 0.8,
})
.addTo(map)
.bindPopup(`<strong>${loc.name}</strong><br/>Type: ${loc.type}`);
});
const allCoords = locations.map((loc) => loc.coords);
map.fitBounds(L.latLngBounds(allCoords), { padding: [50, 50] });
Coordinate Format
Leaflet uses [latitude, longitude] format:
- Latitude: -90 to 90 (South to North)
- Longitude: -180 to 180 (West to East)
Examples:
- New York:
[40.7128, -74.0060]
- London:
[51.5074, -0.1278]
- Tokyo:
[35.6762, 139.6503]
- Sydney:
[-33.8688, 151.2093]
Common mistake: Reversing coordinates to [lng, lat] - always use [lat, lng]!
Built-in Features
- Pan/zoom - Click and drag to pan, scroll to zoom
- Zoom buttons - +/- controls in top-left
- Reset view - Button to restore initial view
- Code viewer - Toggle to show/hide source
- Responsive - Automatically resizes with window
Zoom Levels
- 1: World view
- 5: Continent
- 10: City
- 15: Streets
- 18: Buildings
Best Practices
- Always add tile layer - Map is blank without it
- Use appropriate zoom - 1 (world) to 18 (street level)
- Use fitBounds() for multi-point maps - Shows all features
- Embed data in code - Include coordinates directly
- Verify coordinate order -
[lat, lng] not [lng, lat]
Troubleshooting
Map appears blank/gray
- Ensure tile layer is added with
.addTo(map)
- Check coordinates are valid (
[lat, lng] format)
- Verify internet connection (tiles load from CDN)
- Check browser console for 404 errors on tiles
Markers don't appear
- Check coordinate format:
[latitude, longitude]
- Ensure
.addTo(map) is called on marker
- Verify coordinates are in current view
- Try using fitBounds() to see all markers
Tiles don't load
- Check internet connection
- Verify tile URL is correct
- Look for 404 errors in browser console
- Try a different tile provider
Wrong location shown
- Verify coordinates are correct
- Check coordinate order (lat first, lng second)
- Ensure zoom level is appropriate
Technical Details
File Requirements
- File extension:
.leaflet
- Maximum size: 10MB (configurable)
- Valid JavaScript code
- Self-contained (no external data files)
Browser Compatibility
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Requires JavaScript enabled
- Requires internet connection for tiles
- CDN-dependent: Leaflet v1.9.4
Output
The skill generates a standalone HTML file at:
.preview-skills/leaflet/{filename}.html
Development
This skill is standalone and includes all dependencies:
- Shared libraries bundled in
lib/
- Templates bundled in
templates/
- External CDN dependencies: Leaflet v1.9.4
To modify the skill:
- Edit
config.sh for configuration
- Edit
templates/scripts/leaflet-renderer.js for behavior
- Edit
templates/styles/leaflet.css for styling
- Run
run.sh to test changes
Learn More