Chapter 16 · Mapbox Style Quality
Subchapter 16.4
AGENTS.mdMarkdown6 KBView on GitHub
Quick reference for style validation, accessibility, performance optimization, and testing.
✅ Valid version 8 style specification ✅ At least one source defined ✅ At least one layer defined ✅ Valid layer types and properties ✅ Proper source references in layers
// ❌ Layer references non-existent source
{
"id": "layer",
"source": "missing-source" // Error!
}
// ❌ Invalid property values
{
"paint": {
"fill-color": "not-a-color" // Error!
}
}
// ✅ Valid layer
{
"id": "layer",
"source": "valid-source",
"type": "fill",
"paint": {
"fill-color": "#ff0000"
}
}Requirements:
// ✅ Good contrast for labels
'text-color': '#222222',
'text-halo-color': '#ffffff',
'text-halo-width': 2,
'text-halo-blur': 1Test for:
Rules:
Mobile requirements:
// ✅ Large enough tap targets
'icon-size': 1.2, // Larger icons
'icon-allow-overlap': false, // No overlapping
'symbol-spacing': 250 // Space between symbolsRule: Minimize layer count
Optimization:
// ❌ Multiple layers for categories
map.addLayer({ id: 'parks', filter: ['==', 'type', 'park'] });
map.addLayer({ id: 'water', filter: ['==', 'type', 'water'] });
// ✅ One layer with data-driven styling
map.addLayer({
id: 'features',
paint: {
'fill-color': ['match', ['get', 'type'], 'park', '#90EE90', 'water', '#87CEEB', '#CCCCCC']
}
});// ✅ Set appropriate zoom ranges
{
"type": "vector",
"tiles": ["https://..."],
"minzoom": 0,
"maxzoom": 14 // Don't over-fetch
}
// ✅ Use generateId for feature state
{
"type": "geojson",
"data": geojson,
"generateId": true // Better performance
}// ✅ Use data-driven expressions efficiently
'circle-radius': [
'interpolate', ['linear'], ['zoom'],
8, 2,
16, 8
]
// ❌ Avoid expensive operations in expressions
'circle-radius': [
'sqrt', // Expensive!
['*', ['get', 'value'], ['get', 'multiplier']]
]✅ Test at multiple zoom levels (0, 5, 10, 15, 20) ✅ Test with different data densities ✅ Check label collisions ✅ Verify symbol/icon rendering ✅ Test on desktop and mobile viewports ✅ Check dark mode compatibility
// ✅ Validate style loads
map.on('style.load', () => {
console.log('Style loaded successfully');
});
// ✅ Check for missing resources
map.on('error', (e) => {
console.error('Style error:', e);
});
// ✅ Validate sources
const sources = map.getStyle().sources;
Object.keys(sources).forEach((id) => {
console.log('Source:', id, sources[id]);
});
// ✅ Validate layers
const layers = map.getStyle().layers;
layers.forEach((layer) => {
console.log('Layer:', layer.id, 'Type:', layer.type);
});// ✅ Measure style load time
const startTime = performance.now();
map.setStyle(style);
map.once('idle', () => {
console.log('Style load time:', performance.now() - startTime, 'ms');
});
// ✅ Monitor frame rate
const fps = map.getFPS();
console.log('FPS:', fps); // Should be close to 60
// ✅ Check layer count
console.log('Layer count:', map.getStyle().layers.length);// ❌ Overlapping labels
'text-allow-overlap': true // Bad for readability
// ✅ Prevent collisions
'text-allow-overlap': false,
'text-padding': 2,
'symbol-spacing': 250// ❌ Different styles for similar features
layer1: { 'line-width': 2 }
layer2: { 'line-width': 3 } // Inconsistent
// ✅ Consistent styling
'line-width': [
'match', ['get', 'class'],
'primary', 4,
'secondary', 2,
1
]// ❌ No error handling
map.addSource('source', sourceData);
// ✅ Check before adding
if (!map.getSource('source')) {
map.addSource('source', sourceData);
}Issues:
Solutions:
Validate Structure
Test Accessibility
Optimize Performance
Test Across Devices
Monitor in Production
Validation:
Accessibility:
Performance:
Slow rendering? → Reduce layer count, simplify geometry
Label collisions? → Increase text-padding, reduce symbol density
Poor mobile performance? → Use vector tiles, reduce complexity
Low contrast? → Add text halos, adjust colors
Icons not loading? → Check sprite paths, add error handlers