← Files MapboxARCHIVED FILE
skills/mapbox-style-quality/AGENTS.md
6.25 KB · Oct 2, 2026 · 00:26 UTC
# Mapbox Style Quality Guide
Quick reference for style validation, accessibility, performance optimization, and testing.
## Style Validation Rules
### Required Elements
✅ 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
### Common Errors
```javascript
// ❌ 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"
}
}
```
## Accessibility Standards
### Color Contrast (WCAG 2.1)
**Requirements:**
- Normal text: 4.5:1 contrast ratio
- Large text (18pt+): 3:1 contrast ratio
- UI components: 3:1 contrast ratio
```javascript
// ✅ Good contrast for labels
'text-color': '#222222',
'text-halo-color': '#ffffff',
'text-halo-width': 2,
'text-halo-blur': 1
```
### Colorblind Accessibility
**Test for:**
- Deuteranopia (red-green, 8% of males)
- Protanopia (red-green, 1% of males)
- Tritanopia (blue-yellow, rare)
**Rules:**
- ❌ Don't rely on color alone
- ✅ Add patterns, labels, or symbols
- ✅ Use colorblind-safe palettes
- ✅ Test with colorblind simulators
### Touch Targets
**Mobile requirements:**
- Minimum: 44x44 pixels (iOS)
- Recommended: 48x48 pixels (Material Design)
```javascript
// ✅ Large enough tap targets
'icon-size': 1.2, // Larger icons
'icon-allow-overlap': false, // No overlapping
'symbol-spacing': 250 // Space between symbols
```
## Performance Optimization
### Layer Count
**Rule:** Minimize layer count
- **Good:** < 50 layers
- **Warning:** 50-100 layers
- **Problem:** > 100 layers
**Optimization:**
```javascript
// ❌ 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']
}
});
```
### Source Optimization
```javascript
// ✅ 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
}
```
### Paint Properties
```javascript
// ✅ 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']]
]
```
## Style Testing Checklist
### Visual Testing
✅ 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
### Functional Testing
```javascript
// ✅ 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);
});
```
### Performance Testing
```javascript
// ✅ 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);
```
## Common Quality Issues
### 1. Label Collisions
```javascript
// ❌ Overlapping labels
'text-allow-overlap': true // Bad for readability
// ✅ Prevent collisions
'text-allow-overlap': false,
'text-padding': 2,
'symbol-spacing': 250
```
### 2. Inconsistent Styling
```javascript
// ❌ 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
]
```
### 3. Missing Error Handling
```javascript
// ❌ No error handling
map.addSource('source', sourceData);
// ✅ Check before adding
if (!map.getSource('source')) {
map.addSource('source', sourceData);
}
```
### 4. Poor Mobile Performance
**Issues:**
- Too many layers
- Large GeoJSON files
- High-resolution images
- Complex expressions
**Solutions:**
- Simplify geometry
- Use vector tiles
- Optimize images
- Cache data
## Style Optimization Workflow
1. **Validate Structure**
- Check JSON syntax
- Verify all sources referenced
- Validate property types
2. **Test Accessibility**
- Check color contrast
- Test colorblind modes
- Verify touch targets
3. **Optimize Performance**
- Reduce layer count
- Simplify expressions
- Set appropriate zoom ranges
- Use feature state
4. **Test Across Devices**
- Desktop browsers
- Mobile browsers
- Different screen sizes
- Low-end devices
5. **Monitor in Production**
- Track load times
- Monitor errors
- Check tile request counts
- Measure FPS
## Tools & Resources
**Validation:**
- Mapbox Style Specification: <https://docs.mapbox.com/mapbox-gl-js/style-spec/>
- JSON Schema validators
**Accessibility:**
- WebAIM Contrast Checker
- Coblis Color Blindness Simulator
- WAVE Accessibility Evaluation Tool
**Performance:**
- Chrome DevTools Performance tab
- Mapbox GL JS Performance metrics
- Bundle size analyzers
## Quick Fixes
**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
SHA-256: 6b77436971395874947bc155ed4fb4953c5b37fd4c4f2a53bcf3fd5382b8bfbf