SKILL.md
react-map-gl (MapLibre)
Conventions for react-map-gl/maplibre in FixMyBerlin / FMC projects. Import from react-map-gl/maplibre (not react-map-gl alone). Always import maplibre-gl/dist/maplibre-gl.css.
When to apply
- Adding or changing
<Map>,<Source>,<Layer>, controls, markers - Click/hover/feature inspection wired to map events
- Syncing viewport to URL (
map=zoom/lat/lng) - Child components that need the map instance (feature state, fitBounds, queryRenderedFeatures)
- Debugging “map half loaded” errors, missing style images, or wrong click targets
- Highlighting selected/hovered features (
setFeatureStatevs layerfilter) - Reviewing code that uses
useEffectto sync map camera, clicks, or hover - Code calling
map.addSource,map.addLayer, or effect-driven layer lifecycle - Agent or E2E inspection of layer order, zoom, or rendered features (
window.__mainMap)
Reference reading order
- [declarative-source-layer.md](references/declarative-source-layer.md) —
<Source>/<Layer>only; nevermap.addSource/map.addLayer - [map-event-handlers.md](references/map-event-handlers.md) — which
<Map>handler for which use case; not useEffect - [map-provider-wrapper.md](references/map-provider-wrapper.md) — MapProvider parent; useMap(); never manual refs
- [interactive-layer-ids.md](references/interactive-layer-ids.md) —
interactiveLayerIds+event.features(not raw MapLibre) - [map-loaded-hook.md](references/map-loaded-hook.md) — guard map API calls until
onLoad/useMapLoaded() - [map-debug-exposure.md](references/map-debug-exposure.md) —
window.__mainMapin dev/Playwright;exposeMainMapForDebugginginonLoad - [feature-state.md](references/feature-state.md) —
setFeatureStatevs Reactfilter; reset/diff; inspector sync - [flat-source-layer.md](references/flat-source-layer.md) — sibling
<Source>+<Layer>, required props - [layer-visibility-vs-unmount.md](references/layer-visibility-vs-unmount.md) —
layout.visibilityvs conditional render - [map-props-attribution-locale.md](references/map-props-attribution-locale.md) —
attributionControl={false},locale,RTLTextPlugin - [cursor-handling.md](references/cursor-handling.md) —
cursorprop from hover state - [initial-view-state.md](references/initial-view-state.md) — uncontrolled
initialViewStatevs URL-driven vs bounds - [map-url-state.md](references/map-url-state.md) — Map ↔ URL sync (
onMoveEnd); URL format →tanstack-router-conventions→map-search-param.md - [map-images-missing.md](references/map-images-missing.md) —
styleimagemissing+ dynamic icons - [map-images-proactive.md](references/map-images-proactive.md) — proactive
addImagefor known sprites (vzk-bw)
Pair with: skill tanstack-router-conventions (?map= URL contract), skill nuqs (legacy URL parsers), skill zustand-state-management (map UI store like useMapLoaded), skill playwright-skill (E2E map helpers, getMapLayerIds).
Non-negotiable rules
| Topic | Rule |
|---|---|
| Sources / layers | <Source> + <Layer> in JSX only. Never map.addSource / map.addLayer (or remove\*) for app data. Show/hide via layout.visibility or conditional render — see declarative-source-layer.md. |
| Map events | Camera, pointer, load, and tile lifecycle → <Map> callback props. Not useEffect + map.on(…) for the same behavior. |
| Map access | Wrap app map UI in <MapProvider>. Use useMap() keyed by <Map id="…">. Never pass ref to <Map> for child access. |
| Map ready | Guard getStyle, queryRenderedFeatures, setFeatureState, etc. with useMapLoaded() (set in onLoad). |
| Debug exposure | In onLoad, call exposeMainMapForDebugging(event.target) (dev + Playwright only). Agents/tests read window.__mainMap — see map-debug-exposure.md. |
| Layers | Flat siblings: one <Source> per source id, then <Layer> siblings (not nested layers under Source for refactorability). |
| Clicks | Put layer ids in interactiveLayerIds; read event.features in handlers — do not call queryRenderedFeatures for primary click picking unless syncing URL features. |
| Feature highlight | Inspector/form truth in React; map tint via setFeatureState (paint) or React filter layer — see feature-state.md. Clear old state before applying new. |
| Attribution | attributionControl={false} on <Map>; add <AttributionControl compact> as child. compact is not start-closed — see map-props-attribution-locale.md. |
| Viewport URL | Serialize zoom/lat/lng with zoom-aware lat/lng rounding; write on onMoveEnd with history: 'replace'. |
| View state | Prefer uncontrolled initialViewState seeded from URL; do not mirror full viewState in React state unless you need programmatic camera control. |
Quick import
import 'maplibre-gl/dist/maplibre-gl.css'
import { MapProvider, Map, Source, Layer, useMap } from 'react-map-gl/maplibre'