← MapboxCONTENT HISTORY

Update to Mapbox

Snapshot Sep 30, 2026 · 23:11 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "mapbox-store-locator-patterns",
  "description": "Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance calculation, and interactive lists.",
  "included_files": [
    {
      "relative_path": "AGENTS.md",
      "size_in_bytes": 8683
    },
    {
      "relative_path": "evals/evals.json",
      "size_in_bytes": 3685
    },
    {
      "relative_path": "references/geolocation-directions.md",
      "size_in_bytes": 5354
    },
    {
      "relative_path": "references/markers.md",
      "size_in_bytes": 4399
    },
    {
      "relative_path": "references/optimization-a11y.md",
      "size_in_bytes": 2936
    },
    {
      "relative_path": "references/search-filter.md",
      "size_in_bytes": 1691
    },
    {
      "relative_path": "references/styling-layout.md",
      "size_in_bytes": 4426
    },
    {
      "relative_path": "references/variations-react.md",
      "size_in_bytes": 3412
    }
  ],
  "skill_md_contents": "---\nname: mapbox-store-locator-patterns\ndescription: Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance calculation, and interactive lists.\n---\n\n# Store Locator Patterns Skill\n\nComprehensive patterns for building store locators, restaurant finders, and location-based search applications with Mapbox GL JS. Covers marker display, filtering, distance calculation, interactive lists, and directions integration.\n\n## When to Use This Skill\n\nUse this skill when building applications that:\n\n- Display multiple locations on a map (stores, restaurants, offices, etc.)\n- Allow users to filter or search locations\n- Calculate distances from user location\n- Provide interactive lists synced with map markers\n- Show location details in popups or side panels\n- Integrate directions to selected locations\n\n## Dependencies\n\n**Required:**\n\n- Mapbox GL JS v3.x\n- [@turf/turf](https://turfjs.org/) - For spatial calculations (distance, area, etc.)\n\n**Installation:**\n\n```bash\nnpm install mapbox-gl @turf/turf\n```\n\n## Core Architecture\n\n### Pattern Overview\n\nA typical store locator consists of:\n\n1. **Map Display** - Shows all locations as markers\n2. **Location Data** - GeoJSON with store/location information\n3. **Interactive List** - Side panel listing all locations\n4. **Filtering** - Search, category filters, distance filters\n5. **Detail View** - Popup or panel with location details\n6. **User Location** - Geolocation for distance calculation. For the blue dot location indicator, use the built-in `mapboxgl.GeolocateControl` — simpler than custom markers.\n7. **Directions** - Route to selected location (optional)\n\n### Data Structure\n\n**GeoJSON format for locations:**\n\n```json\n{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"geometry\": {\n        \"type\": \"Point\",\n        \"coordinates\": [-77.034084, 38.909671]\n      },\n      \"properties\": {\n        \"id\": \"store-001\",\n        \"name\": \"Downtown Store\",\n        \"address\": \"123 Main St, Washington, DC 20001\",\n        \"phone\": \"(202) 555-0123\",\n        \"hours\": \"Mon-Sat: 9am-9pm, Sun: 10am-6pm\",\n        \"category\": \"retail\",\n        \"website\": \"https://example.com/downtown\"\n      }\n    }\n  ]\n}\n```\n\n**Key properties:**\n\n- `id` - Unique identifier for each location\n- `name` - Display name\n- `address` - Full address for display and geocoding\n- `coordinates` - `[longitude, latitude]` format\n- `category` - For filtering (retail, restaurant, office, etc.)\n- Custom properties as needed (hours, phone, website, etc.)\n\n## Basic Store Locator Implementation\n\n### Step 1: Initialize Map and Data\n\n```javascript\nimport mapboxgl from 'mapbox-gl';\nimport 'mapbox-gl/dist/mapbox-gl.css';\n\nmapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';\n\n// Store locations data\nconst stores = {\n  type: 'FeatureCollection',\n  features: [\n    {\n      type: 'Feature',\n      geometry: {\n        type: 'Point',\n        coordinates: [-77.034084, 38.909671]\n      },\n      properties: {\n        id: 'store-001',\n        name: 'Downtown Store',\n        address: '123 Main St, Washington, DC 20001',\n        phone: '(202) 555-0123',\n        category: 'retail'\n      }\n    }\n    // ... more stores\n  ]\n};\n\nconst map = new mapboxgl.Map({\n  container: 'map',\n  style: 'mapbox://styles/mapbox/standard',\n  center: [-77.034084, 38.909671],\n  zoom: 11\n});\n```\n\n### Step 2: Add Markers to Map\n\n**Marker strategy by location count:**\n\n| Count               | Strategy                   | Reason                                                                         |\n| ------------------- | -------------------------- | ------------------------------------------------------------------------------ |\n| **Fewer than 100**  | HTML Markers               | Full DOM/CSS control; DOM node count is manageable                             |\n| **100–1,000**       | **Symbol Layer** (default) | Renders on the **GPU via WebGL** — one `<canvas>`, zero per-point DOM elements |\n| **More than 1,000** | Clustering                 | Reduces visual clutter at large scale                                          |\n\n> HTML Markers create one DOM element per point. Beyond ~100 locations the browser spends too much time on layout/paint. Symbol layers bypass the DOM entirely — the GPU draws all points in a single WebGL draw call.\n\n**Symbol Layer implementation** (best for 100–1,000 locations). For HTML Markers (fewer than 100) or Clustering (more than 1,000), see `references/markers.md`.\n\n```javascript\nmap.on('load', () => {\n  // Add store data as source\n  map.addSource('stores', {\n    type: 'geojson',\n    data: stores\n  });\n\n  // Add custom marker image\n  map.loadImage('/marker-icon.png', (error, image) => {\n    if (error) throw error;\n    map.addImage('custom-marker', image);\n\n    // Add symbol layer\n    map.addLayer({\n      id: 'stores-layer',\n      type: 'symbol',\n      source: 'stores',\n      layout: {\n        'icon-image': 'custom-marker',\n        'icon-size': 0.8,\n        'icon-allow-overlap': true,\n        'text-field': ['get', 'name'],\n        'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],\n        'text-offset': [0, 1.5],\n        'text-anchor': 'top',\n        'text-size': 12\n      }\n    });\n  });\n\n  // Handle marker clicks using Interactions API (recommended)\n  map.addInteraction('store-click', {\n    type: 'click',\n    target: { layerId: 'stores-layer' },\n    handler: (e) => {\n      const store = e.feature;\n      flyToStore(store);\n      createPopup(store);\n    }\n  });\n\n  // Or using traditional event listener:\n  // map.on('click', 'stores-layer', (e) => {\n  //   const store = e.features[0];\n  //   flyToStore(store);\n  //   createPopup(store);\n  // });\n  //\n  // For why addInteraction is preferred over map.on(), and how to pair it with\n  // setFeatureState/appearances for hover and selection styling, see the\n  // \"interactions\" reference file in the mapbox-style-patterns skill.\n\n  // Change cursor on hover\n  map.on('mouseenter', 'stores-layer', () => {\n    map.getCanvas().style.cursor = 'pointer';\n  });\n\n  map.on('mouseleave', 'stores-layer', () => {\n    map.getCanvas().style.cursor = '';\n  });\n});\n```\n\n### Step 3: Build Interactive Location List\n\n```javascript\nfunction buildLocationList(stores) {\n  const listingContainer = document.getElementById('listings');\n\n  stores.features.forEach((store, index) => {\n    const listing = listingContainer.appendChild(document.createElement('div'));\n    listing.id = `listing-${store.properties.id}`;\n    listing.className = 'listing';\n\n    const link = listing.appendChild(document.createElement('a'));\n    link.href = '#';\n    link.className = 'title';\n    link.id = `link-${store.properties.id}`;\n    link.innerHTML = store.properties.name;\n\n    const details = listing.appendChild(document.createElement('div'));\n    details.innerHTML = `\n      <p>${store.properties.address}</p>\n      <p>${store.properties.phone || ''}</p>\n    `;\n\n    // Handle listing click\n    link.addEventListener('click', (e) => {\n      e.preventDefault();\n      flyToStore(store);\n      createPopup(store);\n      highlightListing(store.properties.id);\n    });\n  });\n}\n\nfunction flyToStore(store) {\n  map.flyTo({\n    center: store.geometry.coordinates,\n    zoom: 15,\n    duration: 1000\n  });\n}\n\nfunction createPopup(store) {\n  const popups = document.getElementsByClassName('mapboxgl-popup');\n  // Remove existing popups\n  if (popups[0]) popups[0].remove();\n\n  new mapboxgl.Popup({ closeOnClick: true })\n    .setLngLat(store.geometry.coordinates)\n    .setHTML(\n      `<h3>${store.properties.name}</h3>\n       <p>${store.properties.address}</p>\n       <p>${store.properties.phone}</p>\n       ${store.properties.website ? `<a href=\"${store.properties.website}\" target=\"_blank\">Visit Website</a>` : ''}`\n    )\n    .addTo(map);\n}\n\n// IMPORTANT: highlightListing MUST include scrollIntoView — without it,\n// selecting a marker on the map won't scroll the sidebar to the listing.\nfunction highlightListing(id) {\n  // Remove existing highlights\n  const activeItem = document.getElementsByClassName('active');\n  if (activeItem[0]) {\n    activeItem[0].classList.remove('active');\n  }\n\n  // Add highlight to selected listing\n  const listing = document.getElementById(`listing-${id}`);\n  listing.classList.add('active');\n\n  // Scroll the selected listing into view (critical UX requirement)\n  listing.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n}\n\n// Build the list on load\nmap.on('load', () => {\n  buildLocationList(stores);\n});\n```\n\n## Reference Files\n\nLoad these references for additional patterns as needed:\n\n| Reference                 | File                                   | Contents                                                         |\n| ------------------------- | -------------------------------------- | ---------------------------------------------------------------- |\n| HTML Markers & Clustering | `references/markers.md`                | HTML Markers (< 100 locations), Clustering (> 1000 locations)    |\n| Search & Filter           | `references/search-filter.md`          | Text search, category filter                                     |\n| Geolocation & Directions  | `references/geolocation-directions.md` | User location, distance calculation, route directions            |\n| Styling & Layout          | `references/styling-layout.md`         | Full HTML/CSS layout, custom marker CSS                          |\n| Performance & A11y        | `references/optimization-a11y.md`      | Debounced search, data management, error handling, accessibility |\n| Variations & React        | `references/variations-react.md`       | Mobile-first, fullscreen, map-only, React implementation         |\n\n## Resources\n\n- [Turf.js](https://turfjs.org/) - Spatial analysis library (recommended for distance calculations)\n- [Mapbox GL JS API](https://docs.mapbox.com/mapbox-gl-js/)\n- [Interactions API Guide](https://docs.mapbox.com/mapbox-gl-js/guides/user-interactions/interactions/)\n- [GeoJSON Specification](https://geojson.org/)\n- [Directions API](https://docs.mapbox.com/api/navigation/directions/)\n- [Store Locator Tutorial](https://docs.mapbox.com/help/tutorials/building-a-store-locator/)\n"
}

SHA-256: 932f35424c8c4fcdb833098b6e775801bb525b7cf44b028a9b637de7fe8512f6