
Mapbox Style Patterns
- 1.7k installs
- 71 repo stars
- Updated August 4, 2026
- mapbox/mapbox-agent-skills
Reusable Mapbox GL style layer configurations and design patterns for six common mapping use cases.
About
Mapbox Style Patterns provides battle-tested style and layer configurations for six common mapping scenarios: restaurant/POI finders, real estate, data visualization, navigation, dark mode, and delivery/logistics tracking. Developers use this skill to avoid building styles from scratch by reference a pattern library with specific layer recipes, zoom-optimized filters, and visual requirements for each use case. Key workflows include selecting the appropriate pattern via a decision tree based on content type and viewing environment, implementing the recommended layer stack with correct paint properties and filters, and testing across zoom levels and device sizes using the provided checklist.
- Six battle-tested patterns with JSON layer configurations (restaurant markers, real estate boundaries, data viz, navigat
- Decision tree guides pattern selection by content type, viewing environment, user action, and platform
- Zoom-optimized layer performance patterns that simplify features at lower zoom levels
- Visual regression testing checklist covering zoom levels 4-20, mobile/desktop viewports, data density, labels, contrast,
- Reference files for each pattern stored in references/ directory with POI clustering, 3D buildings, terrain, and custom
Mapbox Style Patterns by the numbers
- 1,695 all-time installs (skills.sh)
- +75 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #281 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
mapbox-style-patterns capabilities & compatibility
- Capabilities
- provide pattern library with 6 common mapping us · generate decision tree for pattern selection · supply json layer configurations with filters an · include zoom optimized layer performance pattern · visual regression testing checklist
- Use cases
- ui design · web design · frontend
- Platforms
- macOS · Windows · Linux
- Runs
- Runs locally
- Pricing
- Free
What mapbox-style-patterns says it does
This skill provides battle-tested style patterns and layer configurations for common mapping scenarios.
npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-style-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 71 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | mapbox/mapbox-agent-skills ↗ |
What it does
Apply proven Mapbox style layer configurations for restaurant finders, real estate, data visualization, navigation, and delivery tracking maps.
Who is it for?
Web and mobile apps requiring Mapbox GL maps with common use cases; developers unfamiliar with Mapbox styling; rapid prototyping of map-based features.
Skip if: Custom artistic/experimental styles; non-Mapbox GL libraries; static map images; server-side tile rendering.
When should I use this skill?
Starting a new map style for a specific use case; looking for layer configuration examples; implementing common mapping patterns; optimizing existing Mapbox styles; debugging style issues.
What you get
Developer quickly implements a map style matched to use case (restaurant finder, real estate, data viz, etc.) with correct layers, filters, and paint properties.
- Selected pattern JSON (layers array with paint and layout properties)
- Decision tree choice rationale
- Tested style at 5 zoom levels
By the numbers
- 6 built-in patterns (restaurant/POI, real estate, data viz, navigation, dark mode, delivery/logistics)
- 7 reference files with additional configurations and recipes
- 5 required zoom levels for testing (4, 8, 12, 16, 20)
Files
Mapbox Style Patterns Skill
This skill provides battle-tested style patterns and layer configurations for common mapping scenarios.
Pattern Library
Pattern 1: Restaurant/POI Finder
Use case: Consumer app showing restaurants, cafes, bars, or other points of interest
Visual requirements:
- POIs must be immediately visible
- Street context for navigation
- Neutral background (photos/content overlay)
- Mobile-optimized
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#f5f5f5"
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#d4e4f7",
"fill-opacity": 0.6
}
},
{
"id": "landuse-parks",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "landuse",
"filter": ["==", "class", "park"],
"paint": {
"fill-color": "#e8f5e8",
"fill-opacity": 0.5
}
},
{
"id": "roads-minor",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": ["in", "class", "street", "street_limited"],
"paint": {
"line-color": "#e0e0e0",
"line-width": {
"base": 1.5,
"stops": [
[12, 0.5],
[15, 2],
[18, 6]
]
}
}
},
{
"id": "roads-major",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": ["in", "class", "primary", "secondary", "tertiary"],
"paint": {
"line-color": "#ffffff",
"line-width": {
"base": 1.5,
"stops": [
[10, 1],
[15, 4],
[18, 12]
]
}
}
},
{
"id": "restaurant-markers",
"type": "symbol",
"source": "restaurants",
"layout": {
"icon-image": "restaurant-15",
"icon-size": 1.5,
"icon-allow-overlap": false,
"text-field": ["get", "name"],
"text-offset": [0, 1.5],
"text-size": 12,
"text-allow-overlap": false
},
"paint": {
"icon-color": "#FF6B35",
"text-color": "#333333",
"text-halo-color": "#ffffff",
"text-halo-width": 2
}
}
]
}Key features:
- Desaturated base map (doesn't compete with photos)
- High-contrast markers (#FF6B35 orange stands out)
- Clear road network (white on light gray)
- Parks visible but subtle
- Text halos for readability
Pattern Selection Guide
Decision Tree
Question 1: What is the primary content?
- User-generated markers/pins -> POI Finder Pattern
- Property data/boundaries -> Real Estate Pattern
- Statistical/analytical data -> Data Visualization Pattern
- Routes/directions -> Navigation Pattern
- Real-time tracking/delivery zones -> Delivery/Logistics Pattern (customer markers should include a pulse animation via second circle layer + requestAnimationFrame + setPaintProperty; see references/delivery-logistics.md)
Question 2: What is the viewing environment?
- Daytime/office -> Light theme
- Night/dark environment -> Dark Mode Pattern
- Variable -> Provide theme toggle
Question 3: What is the user's primary action?
- Browse/explore -> Focus on POIs, rich detail
- Navigate -> Focus on roads, route visibility
- Track delivery/logistics -> Real-time updates, zones, status
- Analyze data -> Minimize base map, maximize data
- Select location -> Clear boundaries, context
Question 4: What is the platform?
- Mobile -> Simplified, larger touch targets, less detail
- Desktop -> Can include more detail and complexity
- Both -> Design mobile-first, enhance for desktop
Layer Optimization Patterns
Performance Pattern: Simplified by Zoom
{
"id": "roads",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": [
"step",
["zoom"],
["in", "class", "motorway", "trunk"],
8,
["in", "class", "motorway", "trunk", "primary"],
12,
["in", "class", "motorway", "trunk", "primary", "secondary"],
14,
true
],
"paint": {
"line-width": {
"base": 1.5,
"stops": [
[4, 0.5],
[10, 1],
[15, 4],
[18, 12]
]
}
}
}Reference Files
Additional patterns and configurations are available in the references/ directory. Load the relevant file when a specific pattern is needed.
| File | Contents |
|---|---|
| references/real-estate.md | Pattern 2: Real Estate Map -- property boundaries, price color-coding, amenity markers |
| references/data-viz-base.md | Pattern 3: Data Visualization Base Map -- minimal grayscale base for choropleth/heatmap overlays |
| references/navigation.md | Pattern 4: Navigation/Routing Map -- route display, user location, turn arrows |
| references/dark-mode.md | Pattern 5: Dark Mode / Night Theme -- near-black background, reduced brightness |
| references/delivery-logistics.md | Pattern 6: Delivery/Logistics Map -- real-time tracking, zones, driver markers, ETA badges |
| references/expressions-clustering.md | Data-driven expression patterns + clustering for dense POIs |
| references/common-modifications.md | 3D Buildings, Terrain/Hillshade, Custom Markers |
Loading instructions: Read the reference file that matches the user's use case. For example, if implementing a delivery tracking map, load references/delivery-logistics.md.
Testing Patterns
Visual Regression Checklist
- [ ] Test at zoom levels: 4, 8, 12, 16, 20
- [ ] Verify on mobile (375px width)
- [ ] Verify on desktop (1920px width)
- [ ] Test with dense data
- [ ] Test with sparse data
- [ ] Check label collision
- [ ] Verify color contrast (WCAG)
- [ ] Test loading performance
When to Use This Skill
Invoke this skill when:
- Starting a new map style for a specific use case
- Looking for layer configuration examples
- Implementing common mapping patterns
- Optimizing existing styles
- Need proven recipes for typical scenarios
- Debugging style issues
- Learning Mapbox style best practices
Mapbox Style Patterns
Quick reference for common style patterns, layer configurations, and data-driven styling.
Layer Types Quick Reference
| Layer Type | Use For | Key Properties |
|---|---|---|
| fill | Polygons (countries, parks) | fill-color, fill-opacity |
| line | Roads, boundaries | line-color, line-width |
| symbol | Labels, icons | text-field, icon-image |
| circle | Points (markers, heatmap) | circle-radius, circle-color |
| heatmap | Density visualization | heatmap-intensity, heatmap-color |
| fill-extrusion | 3D buildings | fill-extrusion-height |
| raster | Satellite, aerial imagery | raster-opacity |
Data-Driven Styling Patterns
Based on Property Value
// ✅ Color by category
'fill-color': [
'match',
['get', 'type'],
'park', '#90EE90',
'water', '#87CEEB',
'urban', '#D3D3D3',
'#CCCCCC' // default
]
// ✅ Size by numeric value
'circle-radius': [
'interpolate', ['linear'],
['get', 'population'],
0, 5,
1000000, 20
]Based on Zoom Level
// ✅ Show/hide by zoom
'visibility': [
'step',
['zoom'],
'none', // Hidden below zoom 10
10, 'visible'
]
// ✅ Size by zoom
'text-size': [
'interpolate', ['linear'],
['zoom'],
8, 10, // Small at zoom 8
16, 18 // Large at zoom 16
]Common Patterns
1. Clustering
map.addSource('points', {
type: 'geojson',
data: geojson,
cluster: true,
clusterRadius: 50,
clusterMaxZoom: 14
});
// Cluster circles
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points',
filter: ['has', 'point_count'],
paint: {
'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'],
'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40]
}
});2. Feature State (Hover/Selection)
// ✅ Hover effect without modifying data
map.on('mousemove', 'layer', (e) => {
if (hoveredId) {
map.setFeatureState(
{ source: 'source', id: hoveredId },
{ hover: false }
);
}
hoveredId = e.features[0].id;
map.setFeatureState(
{ source: 'source', id: hoveredId },
{ hover: true }
);
});
// Style based on state
'fill-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#0080ff', // Hover color
'#3bb2d0' // Default color
]3. Filters
// ✅ Filter by property
map.setFilter('layer', ['==', ['get', 'type'], 'restaurant']);
// ✅ Filter by multiple conditions
map.setFilter('layer', ['all', ['==', ['get', 'type'], 'restaurant'], ['>', ['get', 'rating'], 4]]);
// ✅ Filter by zoom
map.setFilter('layer', ['all', ['>=', ['zoom'], 10], ['<', ['zoom'], 14]]);4. Expressions
// ✅ Conditional styling
'circle-color': [
'case',
['<', ['get', 'value'], 10], '#00ff00', // Green if < 10
['<', ['get', 'value'], 20], '#ffff00', // Yellow if < 20
'#ff0000' // Red otherwise
]
// ✅ Math operations
'circle-radius': [
'*',
['sqrt', ['get', 'population']],
0.01
]
// ✅ String concatenation
'text-field': ['concat', 'Population: ', ['get', 'pop']]Performance Patterns
Vector Tiles vs GeoJSON
Use vector tiles when:
- Large datasets (>5MB)
- Need different zoom levels
- Want server-side updates
Use GeoJSON when:
- Small datasets (<5MB)
- Frequent client-side updates
- Simple implementation needed
Layer Optimization
// ✅ Set minzoom/maxzoom
map.addLayer({
id: 'layer',
minzoom: 10, // Only show zoom 10+
maxzoom: 16 // Hide above zoom 16
});
// ✅ Use feature state instead of removing/re-adding
// Bad: map.removeLayer() / map.addLayer()
// Good: map.setFeatureState()
// ✅ Combine similar layers
// Bad: Separate layer for each category
// Good: One layer with data-driven stylingStyle Management
Dynamic Style Updates
// ✅ Update paint property
map.setPaintProperty('layer', 'fill-color', '#ff0000');
// ✅ Update layout property
map.setLayoutProperty('layer', 'visibility', 'none');
// ✅ Update source data
map.getSource('source').setData(newGeojson);
// ✅ Batch updates (better performance)
map.once('idle', () => {
// Multiple style changes here
});Before Layers
// ✅ Insert layer at specific position
map.addLayer(
{
id: 'new-layer',
type: 'fill',
source: 'source'
},
'existing-layer-id'
); // Insert before this layerCommon Use Cases
Choropleth Map
map.addLayer({
id: 'choropleth',
type: 'fill',
source: 'counties',
paint: {
'fill-color': ['interpolate', ['linear'], ['get', 'density'], 0, '#f7fbff', 100, '#08519c'],
'fill-opacity': 0.7
}
});Route Visualization
map.addLayer({
id: 'route',
type: 'line',
source: 'route',
paint: {
'line-color': '#0080ff',
'line-width': 5,
'line-opacity': 0.8
}
});3D Buildings
map.addLayer({
id: 'buildings',
type: 'fill-extrusion',
source: 'composite',
'source-layer': 'building',
paint: {
'fill-extrusion-color': '#aaa',
'fill-extrusion-height': ['get', 'height'],
'fill-extrusion-base': ['get', 'min_height'],
'fill-extrusion-opacity': 0.8
}
});Quick Reference: Expression Types
| Type | Function | Example |
|---|---|---|
| Decision | match, case | Color by category |
| Ramp | interpolate, step | Size by value |
| Math | +, -, \*, /, % | Calculate values |
| String | concat, upcase, downcase | Format labels |
| Lookup | get, has, at | Access properties |
| Zoom | zoom | Zoom-based styling |
Debugging Tips
// ✅ Check if layer exists
if (map.getLayer('layer-id')) {
map.removeLayer('layer-id');
}
// ✅ Check if source exists
if (map.getSource('source-id')) {
map.removeSource('source-id');
}
// ✅ List all layers
console.log(map.getStyle().layers);
// ✅ Get layer paint properties
console.log(map.getPaintProperty('layer', 'fill-color'));{
"skill_name": "mapbox-style-patterns",
"evals": [
{
"id": 1,
"prompt": "Review this delivery map style implementation and tell me what's wrong with it:\n\n```javascript\nmap.addLayer({\n id: 'delivery-route',\n type: 'line',\n source: 'route',\n paint: {\n 'line-color': '#2196f3',\n 'line-width': 6\n }\n});\n\nmap.addLayer({\n id: 'delivery-zones',\n type: 'fill',\n source: 'zones',\n paint: {\n 'fill-color': '#4caf50',\n 'fill-opacity': 0.2\n }\n});\n\nmap.addLayer({\n id: 'driver-arrow',\n type: 'symbol',\n source: 'drivers',\n layout: {\n 'icon-image': 'arrow',\n 'icon-rotate': ['get', 'bearing'],\n 'icon-rotation-alignment': 'viewport'\n }\n});\n\nmap.addLayer({\n id: 'customer-dot',\n type: 'circle',\n source: 'customer',\n paint: {\n 'circle-radius': 12,\n 'circle-color': '#4caf50',\n 'circle-stroke-color': '#ffffff',\n 'circle-stroke-width': 3\n }\n});\n```",
"expected_output": "Should identify four issues: (1) The route is rendered as a single line layer — the correct pattern is two layers: a wider 'route-casing' layer behind it with a darker color to create a visible outline/border effect; (2) All delivery zones use the same green color — they should use a 'match' expression on the 'status' property to color-code zones (e.g. green=available, orange=busy, red=unavailable); (3) Driver arrow uses 'icon-rotation-alignment': 'viewport' — should be 'map' so the arrow rotates with the map as the user pans/rotates; (4) Customer marker is a static circle — a pulsing ring animation should be added as a second circle layer driven by requestAnimationFrame + setPaintProperty to create the expanding ring effect.",
"files": [],
"expectations": [
"Identifies the missing route-casing layer — route needs two layers (wider casing behind, narrower colored line on top) for a visible outline effect",
"Identifies that all delivery zones use the same color and recommends a 'match' expression on zone status (available/busy/unavailable) for color-coding",
"Identifies 'icon-rotation-alignment': 'viewport' as wrong for driver direction arrows — should be 'map' so arrows rotate with the map",
"Identifies the missing pulse animation on the customer marker — recommends a second circle layer driven by requestAnimationFrame + setPaintProperty"
]
},
{
"id": 2,
"prompt": "I'm building a US county-level choropleth map showing election results by party. The data will be shown as colored county fills (red/blue/purple). What base map style pattern should I use, and what layers should I include?",
"expected_output": "Should recommend the Data Visualization base map pattern: grayscale palette throughout (no competing colors), water in gray (not blue), only major city/capital labels for orientation, admin boundaries (dashed) for geographic reference, minimal or no road detail (only motorways at low zoom levels), low opacity on all base layers. The goal is a minimal base that doesn't compete with the election data overlay colors. Should NOT recommend a standard streets style or colorful base map.",
"files": [],
"expectations": [
"Recommends a minimal, grayscale base map (not a standard streets or colorful style)",
"Recommends gray/desaturated water (not blue) so it doesn't compete with the data colors",
"Recommends including admin boundaries (state/county lines) as the primary geographic reference",
"Recommends limiting road detail — only major roads (motorways) at low zoom, or no roads at all",
"Recommends keeping only major city labels for orientation, not full POI/street label set"
]
},
{
"id": 3,
"prompt": "I'm implementing a food delivery app and need the customer's delivery location to have a pulsing ring animation — like a sonar ping that expands outward and fades. How do I implement this with Mapbox GL JS using a circle layer (not an HTML marker)?",
"expected_output": "Should describe the requestAnimationFrame + setPaintProperty pattern: create a second 'pulse' circle layer on the same source as the customer marker, then use a requestAnimationFrame loop that updates circle-radius (expanding from base radius to 2x) and circle-opacity (fading from ~0.3 to 0) on each frame using performance.now() to compute the animation phase. Should NOT suggest CSS animations (only work on HTML elements, not canvas layers), Mapbox Expressions (static, can't animate over time), or loading an animated GIF as a sprite.",
"files": [],
"expectations": [
"Recommends a second circle layer (the 'pulse' layer) on the same source as the customer marker",
"Uses requestAnimationFrame for the animation loop",
"Uses map.setPaintProperty() to update circle-radius and circle-opacity each frame",
"Uses performance.now() or elapsed time to compute animation phase (0 to 1 cycling)",
"Does NOT suggest CSS animations, which only work on HTML elements not canvas layers"
]
}
]
}
Common Modifications
Add 3D Buildings
{
"id": "3d-buildings",
"type": "fill-extrusion",
"source": "composite",
"source-layer": "building",
"minzoom": 15,
"paint": {
"fill-extrusion-color": "#aaa",
"fill-extrusion-height": ["interpolate", ["linear"], ["zoom"], 15, 0, 15.05, ["get", "height"]],
"fill-extrusion-base": ["interpolate", ["linear"], ["zoom"], 15, 0, 15.05, ["get", "min_height"]],
"fill-extrusion-opacity": 0.6
}
}Add Terrain/Hillshade
{
"sources": {
"mapbox-dem": {
"type": "raster-dem",
"url": "mapbox://mapbox.mapbox-terrain-dem-v1"
}
},
"layers": [
{
"id": "hillshade",
"type": "hillshade",
"source": "mapbox-dem",
"paint": {
"hillshade-exaggeration": 0.5,
"hillshade-shadow-color": "#000000"
}
}
],
"terrain": {
"source": "mapbox-dem",
"exaggeration": 1.5
}
}Add Custom Markers
{
"id": "custom-markers",
"type": "symbol",
"source": "markers",
"layout": {
"icon-image": "custom-marker",
"icon-size": 0.8,
"icon-anchor": "bottom",
"icon-allow-overlap": true,
"text-field": ["get", "name"],
"text-offset": [0, -2],
"text-anchor": "top",
"text-size": 12
},
"paint": {
"text-color": "#ffffff",
"text-halo-color": "#000000",
"text-halo-width": 2
}
}Pattern 5: Dark Mode / Night Theme
Use case: Reduced eye strain, night use, modern aesthetic, battery saving (OLED)
Visual requirements:
- Dark background
- Reduced brightness
- Maintained contrast
- Readable text
- Comfortable viewing
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#0a0a0a"
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#1a237e",
"fill-opacity": 0.5
}
},
{
"id": "parks",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "landuse",
"filter": ["==", "class", "park"],
"paint": {
"fill-color": "#1b5e20",
"fill-opacity": 0.4
}
},
{
"id": "buildings",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "building",
"paint": {
"fill-color": "#1a1a1a",
"fill-opacity": 0.8,
"fill-outline-color": "#2a2a2a"
}
},
{
"id": "roads-minor",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": ["in", "class", "street", "street_limited"],
"paint": {
"line-color": "#2a2a2a",
"line-width": {
"base": 1.5,
"stops": [
[12, 0.5],
[15, 2],
[18, 6]
]
}
}
},
{
"id": "roads-major",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": ["in", "class", "primary", "secondary", "motorway"],
"paint": {
"line-color": "#3a3a3a",
"line-width": {
"base": 1.5,
"stops": [
[10, 1],
[15, 4],
[18, 12]
]
}
}
},
{
"id": "labels",
"type": "symbol",
"source": "mapbox-streets",
"source-layer": "place_label",
"layout": {
"text-field": ["get", "name"],
"text-size": 12
},
"paint": {
"text-color": "#e0e0e0",
"text-halo-color": "#0a0a0a",
"text-halo-width": 2
}
}
]
}Key features:
- Very dark background (#0a0a0a near-black)
- Subtle color differentiation (deep blues, greens)
- Light text (#e0e0e0) with dark halos
- Reduced opacity throughout
- Easy on eyes in low light
Pattern 3: Data Visualization Base Map
Use case: Choropleth maps, heatmaps, data overlays, analytics dashboards
Visual requirements:
- Minimal base map (data is the focus)
- Context without distraction
- Works with various data overlay colors
- High contrast optional for dark data
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#f0f0f0"
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#d8d8d8",
"fill-opacity": 0.5
}
},
{
"id": "admin-boundaries",
"type": "line",
"source": "mapbox-streets",
"source-layer": "admin",
"filter": ["in", "admin_level", 0, 1, 2],
"paint": {
"line-color": "#999999",
"line-width": {
"base": 1,
"stops": [
[0, 0.5],
[10, 1],
[15, 2]
]
},
"line-dasharray": [3, 2]
}
},
{
"id": "roads-major-simplified",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"filter": ["in", "class", "motorway", "primary"],
"minzoom": 6,
"paint": {
"line-color": "#cccccc",
"line-width": {
"base": 1.2,
"stops": [
[6, 0.5],
[10, 1],
[15, 2]
]
},
"line-opacity": 0.5
}
},
{
"id": "place-labels-major",
"type": "symbol",
"source": "mapbox-streets",
"source-layer": "place_label",
"filter": ["in", "type", "city", "capital"],
"layout": {
"text-field": ["get", "name"],
"text-size": {
"base": 1,
"stops": [
[4, 10],
[10, 14]
]
},
"text-font": ["Open Sans Semibold"]
},
"paint": {
"text-color": "#666666",
"text-halo-color": "#ffffff",
"text-halo-width": 2
}
}
]
}Key features:
- Grayscale palette (doesn't interfere with data colors)
- Minimal detail (roads, borders only)
- Major cities labeled for orientation
- Low opacity throughout
- Perfect for overlay data
Pattern 6: Delivery/Logistics Map
Use case: Food delivery, package delivery, logistics tracking, on-demand services (DoorDash, Uber Eats, courier apps)
Visual requirements:
- Real-time location tracking (drivers, customers)
- Delivery zones clearly defined
- Active routes highly visible
- Status indicators obvious
- Delivery radius visualization
- Performance for live updates
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#fafafa"
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#c6dff5",
"fill-opacity": 0.5
}
},
{
"id": "roads-background",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"paint": {
"line-color": "#e0e0e0",
"line-width": {
"base": 1.5,
"stops": [
[10, 1],
[15, 3],
[18, 8]
]
}
}
},
{
"id": "delivery-zones",
"type": "fill",
"source": "delivery-zones",
"paint": {
"fill-color": [
"match",
["get", "status"],
"available",
"#4caf50",
"busy",
"#ff9800",
"unavailable",
"#f44336",
"#9e9e9e"
],
"fill-opacity": 0.15
}
},
{
"id": "delivery-zone-borders",
"type": "line",
"source": "delivery-zones",
"paint": {
"line-color": [
"match",
["get", "status"],
"available",
"#4caf50",
"busy",
"#ff9800",
"unavailable",
"#f44336",
"#9e9e9e"
],
"line-width": 2,
"line-dasharray": [3, 2]
}
},
{
"id": "delivery-radius",
"type": "fill",
"source": "delivery-radius",
"paint": {
"fill-color": "#2196f3",
"fill-opacity": 0.1
}
},
{
"id": "delivery-radius-border",
"type": "line",
"source": "delivery-radius",
"paint": {
"line-color": "#2196f3",
"line-width": 2,
"line-dasharray": [5, 3]
}
},
{
"id": "active-route",
"type": "line",
"source": "active-route",
"paint": {
"line-color": "#1976d2",
"line-width": {
"base": 1.5,
"stops": [
[10, 4],
[15, 8],
[18, 16]
]
},
"line-opacity": 0.8
}
},
{
"id": "route-progress",
"type": "line",
"source": "route-progress",
"paint": {
"line-color": "#43a047",
"line-width": {
"base": 1.5,
"stops": [
[10, 4],
[15, 8],
[18, 16]
]
}
}
},
{
"id": "restaurant-marker",
"type": "circle",
"source": "pickup-locations",
"paint": {
"circle-radius": 12,
"circle-color": "#ff5722",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 3
}
},
{
"id": "restaurant-icon",
"type": "symbol",
"source": "pickup-locations",
"layout": {
"icon-image": "restaurant-15",
"icon-size": 1.2,
"text-field": ["get", "name"],
"text-offset": [0, 2],
"text-size": 11
},
"paint": {
"text-color": "#212121",
"text-halo-color": "#ffffff",
"text-halo-width": 2
}
},
{
"id": "customer-marker",
"type": "circle",
"source": "delivery-locations",
"paint": {
"circle-radius": 12,
"circle-color": "#4caf50",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 3
}
},
{
"id": "customer-pulse",
"type": "circle",
"source": "delivery-locations",
"paint": {
"circle-radius": {
"base": 1,
"stops": [
[0, 12],
[1, 24]
]
},
"circle-color": "#4caf50",
"circle-opacity": {
"base": 1,
"stops": [
[0, 0.3],
[1, 0]
]
}
}
},
{
"id": "driver-marker-shadow",
"type": "circle",
"source": "driver-locations",
"paint": {
"circle-radius": 14,
"circle-color": "#000000",
"circle-opacity": 0.2,
"circle-translate": [0, 2]
}
},
{
"id": "driver-marker",
"type": "circle",
"source": "driver-locations",
"paint": {
"circle-radius": 14,
"circle-color": [
"match",
["get", "status"],
"picking_up",
"#ff9800",
"en_route",
"#2196f3",
"delivered",
"#4caf50",
"#9e9e9e"
],
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 3
}
},
{
"id": "driver-direction",
"type": "symbol",
"source": "driver-locations",
"layout": {
"icon-image": "arrow",
"icon-size": 0.5,
"icon-rotate": ["get", "bearing"],
"icon-rotation-alignment": "map",
"icon-allow-overlap": true
}
},
{
"id": "eta-badges",
"type": "symbol",
"source": "driver-locations",
"layout": {
"text-field": ["concat", ["get", "eta"], " min"],
"text-size": 11,
"text-offset": [0, -2.5],
"text-allow-overlap": true
},
"paint": {
"text-color": "#ffffff",
"text-halo-color": "#1976d2",
"text-halo-width": 8,
"text-halo-blur": 1
}
}
]
}Key features:
- Color-coded delivery zones (green=available, orange=busy, red=unavailable)
- Real-time driver markers with status colors
- Pulsing customer location indicator
- Active route with completed progress shown in different color
- Delivery radius visualization with dashed border
- ETA badges on driver markers
- Direction arrows showing driver heading
- Restaurant/pickup locations clearly marked
- Shadow effects on driver markers for depth
Load custom arrow icon:
// Load custom arrow icon for driver direction indicator
// Note: 'arrow' is not a standard Maki icon and must be loaded manually
map.on('load', () => {
map.loadImage('path/to/arrow-icon.png', (error, image) => {
if (error) throw error;
map.addImage('arrow', image);
});
});Real-time update pattern:
// Update driver location (call on GPS update)
map.getSource('driver-locations').setData({
type: 'FeatureCollection',
features: drivers.map((driver) => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: driver.location
},
properties: {
id: driver.id,
status: driver.status,
bearing: driver.bearing,
eta: driver.eta
}
}))
});
// Animate route progress
function updateRouteProgress(completedCoordinates) {
map.getSource('route-progress').setData({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: completedCoordinates
}
});
}
// Pulse animation for active delivery
function pulseCustomerMarker() {
const duration = 2000;
const start = performance.now();
function animate(time) {
const elapsed = time - start;
const phase = (elapsed % duration) / duration;
// Update radius (12 to 24 pixels)
map.setPaintProperty('customer-pulse', 'circle-radius', 12 + phase * 12);
// Update opacity (fade from 0.3 to 0)
map.setPaintProperty('customer-pulse', 'circle-opacity', 0.3 * (1 - phase));
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}Performance tips:
- Update driver positions every 3-5 seconds (not every GPS ping)
- Use
setData()instead of removing/re-adding sources - Limit visible drivers to current viewport + buffer
- Debounce rapid updates during high activity
- Use symbol layers instead of HTML markers for 50+ drivers
Expression Patterns and Clustering
Expression Pattern: Data-Driven Styling
{
"paint": {
"circle-radius": [
"interpolate",
["linear"],
["get", "population"],
0,
3,
1000,
5,
10000,
8,
100000,
12,
1000000,
20
],
"circle-color": [
"case",
["<", ["get", "temperature"], 0],
"#2196f3",
["<", ["get", "temperature"], 20],
"#4caf50",
["<", ["get", "temperature"], 30],
"#ffc107",
"#f44336"
]
}
}Clustering Pattern: Handle Dense POIs
{
"id": "clusters",
"type": "circle",
"source": "pois",
"filter": ["has", "point_count"],
"paint": {
"circle-color": [
"step",
["get", "point_count"],
"#51bbd6", 10,
"#f1f075", 30,
"#f28cb1"
],
"circle-radius": [
"step",
["get", "point_count"],
15, 10,
20, 30,
25
]
}
},
{
"id": "cluster-count",
"type": "symbol",
"source": "pois",
"filter": ["has", "point_count"],
"layout": {
"text-field": ["get", "point_count_abbreviated"],
"text-size": 12
}
}Pattern 4: Navigation/Routing Map
Use case: Turn-by-turn directions, route planning, delivery apps
Visual requirements:
- Route highly visible
- Current location always clear
- Turn points obvious
- Street names readable
- Performance optimized
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#ffffff"
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#a8d8ea"
}
},
{
"id": "landuse",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "landuse",
"paint": {
"fill-color": [
"match",
["get", "class"],
"park",
"#d4edda",
"hospital",
"#f8d7da",
"school",
"#fff3cd",
"#e9ecef"
],
"fill-opacity": 0.5
}
},
{
"id": "roads-background",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"paint": {
"line-color": "#333333",
"line-width": {
"base": 1.5,
"stops": [
[10, 2],
[15, 8],
[18, 20]
]
},
"line-opacity": 0.3
}
},
{
"id": "roads-foreground",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"paint": {
"line-color": "#ffffff",
"line-width": {
"base": 1.5,
"stops": [
[10, 1],
[15, 6],
[18, 16]
]
}
}
},
{
"id": "route-casing",
"type": "line",
"source": "route",
"paint": {
"line-color": "#0d47a1",
"line-width": {
"base": 1.5,
"stops": [
[10, 8],
[15, 16],
[18, 32]
]
},
"line-opacity": 0.4
}
},
{
"id": "route-line",
"type": "line",
"source": "route",
"paint": {
"line-color": "#2196f3",
"line-width": {
"base": 1.5,
"stops": [
[10, 6],
[15, 12],
[18, 24]
]
}
}
},
{
"id": "user-location",
"type": "circle",
"source": "user-location",
"paint": {
"circle-radius": 8,
"circle-color": "#2196f3",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 3
}
},
{
"id": "user-location-pulse",
"type": "circle",
"source": "user-location",
"paint": {
"circle-radius": {
"base": 1,
"stops": [
[0, 16],
[1, 24]
]
},
"circle-color": "#2196f3",
"circle-opacity": {
"base": 1,
"stops": [
[0, 0.4],
[1, 0]
]
}
}
},
{
"id": "turn-arrows",
"type": "symbol",
"source": "route-maneuvers",
"layout": {
"icon-image": ["get", "arrow-type"],
"icon-size": 1.5,
"icon-rotation-alignment": "map",
"icon-rotate": ["get", "bearing"]
}
}
]
}Key features:
- Thick, high-contrast route (blue on white)
- Pulsing user location indicator
- Turn arrows at maneuver points
- Simplified background (focus on route)
- Color-coded land use for context
Pattern 2: Real Estate Map
Use case: Property search, neighborhood exploration, real estate listings
Visual requirements:
- Property boundaries clear
- Neighborhood context visible
- Amenities highlighted (schools, parks, transit)
- Price/property data display
Recommended layers:
{
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#fafafa"
}
},
{
"id": "parks-green-spaces",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "landuse",
"filter": ["in", "class", "park", "pitch", "playground"],
"paint": {
"fill-color": "#7cb342",
"fill-opacity": 0.3
}
},
{
"id": "water",
"type": "fill",
"source": "mapbox-streets",
"source-layer": "water",
"paint": {
"fill-color": "#42a5f5",
"fill-opacity": 0.4
}
},
{
"id": "roads",
"type": "line",
"source": "mapbox-streets",
"source-layer": "road",
"paint": {
"line-color": "#e0e0e0",
"line-width": {
"base": 1.2,
"stops": [
[10, 0.5],
[15, 2],
[18, 6]
]
}
}
},
{
"id": "property-boundaries",
"type": "line",
"source": "properties",
"paint": {
"line-color": "#7e57c2",
"line-width": 2,
"line-opacity": 0.8
}
},
{
"id": "property-fills",
"type": "fill",
"source": "properties",
"paint": {
"fill-color": [
"interpolate",
["linear"],
["get", "price"],
200000,
"#4caf50",
500000,
"#ffc107",
1000000,
"#f44336"
],
"fill-opacity": 0.3
}
},
{
"id": "school-icons",
"type": "symbol",
"source": "composite",
"source-layer": "poi_label",
"filter": ["==", "class", "school"],
"layout": {
"icon-image": "school-15",
"icon-size": 1.2
},
"paint": {
"icon-opacity": 0.8
}
},
{
"id": "transit-stops",
"type": "circle",
"source": "transit",
"paint": {
"circle-radius": 6,
"circle-color": "#2196f3",
"circle-stroke-color": "#ffffff",
"circle-stroke-width": 2
}
}
]
}Key features:
- Properties color-coded by price (green->yellow->red)
- Parks prominently visible (important for home buyers)
- Schools and transit clearly marked
- Property boundaries visible
- Clean, professional aesthetic
Related skills
How it compares
Use mapbox-style-patterns for Mapbox GL layer recipes during coding; use geocoding or routing APIs separately when the task is search or navigation backend logic.
FAQ
Which pattern should I use for my delivery tracking app?
Use the Delivery/Logistics Pattern (Pattern 6) if you need real-time tracking, zones, driver markers, and ETA badges. It includes pulse animation via second circle layer and setPaintProperty.
How do I make my map work on both mobile and desktop?
Design mobile-first with simplified features and larger touch targets, then enhance for desktop. Test at viewport widths 375px (mobile) and 1920px (desktop) from the testing checklist.
What zoom levels should I test?
Test at zoom levels 4, 8, 12, 16, and 20 per the visual regression checklist, plus verify label collision and performance at dense and sparse data densities.
Is Mapbox Style Patterns safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.