Labsco
mapbox logo

mapbox-store-locator-patterns

68

by mapbox · part of mapbox/mapbox-agent-skills

Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance…

🔥🔥🔥🔥✓ VerifiedFreeQuick setup
🧩 One of 7 skills in the mapbox/mapbox-agent-skills package — works on its own, and pairs well with its siblings.

Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance…

Inspect the full instructions your agent will receiveExpand

This is the exact playbook injected into your agent when the skill activates — shown here so you can audit it before installing. You don't need to read it to use the skill.

by mapbox

Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance… npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-store-locator-patterns Download ZIPGitHub68

Store Locator Patterns Skill

Comprehensive 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.

When to Use This Skill

Use this skill when building applications that:

  • Display multiple locations on a map (stores, restaurants, offices, etc.)

  • Allow users to filter or search locations

  • Calculate distances from user location

  • Provide interactive lists synced with map markers

  • Show location details in popups or side panels

  • Integrate directions to selected locations

Dependencies

Required:

  • Mapbox GL JS v3.x

  • @turf/turf - For spatial calculations (distance, area, etc.)

Installation:

Copy & paste — that's it
npm install mapbox-gl @turf/turf

Core Architecture

Pattern Overview

A typical store locator consists of:

  • Map Display - Shows all locations as markers

  • Location Data - GeoJSON with store/location information

  • Interactive List - Side panel listing all locations

  • Filtering - Search, category filters, distance filters

  • Detail View - Popup or panel with location details

  • User Location - Geolocation for distance calculation. For the blue dot location indicator, use the built-in mapboxgl.GeolocateControl — simpler than custom markers.

  • Directions - Route to selected location (optional)

Data Structure

GeoJSON format for locations:

Copy & paste — that's it
{
 "type": "FeatureCollection",
 "features": [
 {
 "type": "Feature",
 "geometry": {
 "type": "Point",
 "coordinates": [-77.034084, 38.909671]
 },
 "properties": {
 "id": "store-001",
 "name": "Downtown Store",
 "address": "123 Main St, Washington, DC 20001",
 "phone": "(202) 555-0123",
 "hours": "Mon-Sat: 9am-9pm, Sun: 10am-6pm",
 "category": "retail",
 "website": "https://example.com/downtown"
 }
 }
 ]
}

Key properties:

  • id - Unique identifier for each location

  • name - Display name

  • address - Full address for display and geocoding

  • coordinates - [longitude, latitude] format

  • category - For filtering (retail, restaurant, office, etc.)

  • Custom properties as needed (hours, phone, website, etc.)

Basic Store Locator Implementation

Step 1: Initialize Map and Data

Copy & paste — that's it
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';

// Store locations data
const stores = {
 type: 'FeatureCollection',
 features: [
 {
 type: 'Feature',
 geometry: {
 type: 'Point',
 coordinates: [-77.034084, 38.909671]
 },
 properties: {
 id: 'store-001',
 name: 'Downtown Store',
 address: '123 Main St, Washington, DC 20001',
 phone: '(202) 555-0123',
 category: 'retail'
 }
 }
 // ... more stores
 ]
};

const map = new mapboxgl.Map({
 container: 'map',
 style: 'mapbox://styles/mapbox/standard',
 center: [-77.034084, 38.909671],
 zoom: 11
});

Step 2: Add Markers to Map

Marker strategy by location count:

Count Strategy Reason Fewer than 100 HTML Markers Full DOM/CSS control; DOM node count is manageable 100–1,000 Symbol Layer (default) Renders on the GPU via WebGL — one <canvas>, zero per-point DOM elements More than 1,000 Clustering Reduces visual clutter at large scale

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.

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.

Copy & paste — that's it
map.on('load', () => {
 // Add store data as source
 map.addSource('stores', {
 type: 'geojson',
 data: stores
 });

 // Add custom marker image
 map.loadImage('/marker-icon.png', (error, image) => {
 if (error) throw error;
 map.addImage('custom-marker', image);

 // Add symbol layer
 map.addLayer({
 id: 'stores-layer',
 type: 'symbol',
 source: 'stores',
 layout: {
 'icon-image': 'custom-marker',
 'icon-size': 0.8,
 'icon-allow-overlap': true,
 'text-field': ['get', 'name'],
 'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
 'text-offset': [0, 1.5],
 'text-anchor': 'top',
 'text-size': 12
 }
 });
 });

 // Handle marker clicks using Interactions API (recommended)
 map.addInteraction('store-click', {
 type: 'click',
 target: { layerId: 'stores-layer' },
 handler: (e) => {
 const store = e.feature;
 flyToStore(store);
 createPopup(store);
 }
 });

 // Or using traditional event listener:
 // map.on('click', 'stores-layer', (e) => {
 // const store = e.features[0];
 // flyToStore(store);
 // createPopup(store);
 // });

 // Change cursor on hover
 map.on('mouseenter', 'stores-layer', () => {
 map.getCanvas().style.cursor = 'pointer';
 });

 map.on('mouseleave', 'stores-layer', () => {
 map.getCanvas().style.cursor = '';
 });
});

Step 3: Build Interactive Location List

Copy & paste — that's it
function buildLocationList(stores) {
 const listingContainer = document.getElementById('listings');

 stores.features.forEach((store, index) => {
 const listing = listingContainer.appendChild(document.createElement('div'));
 listing.id = `listing-${store.properties.id}`;
 listing.className = 'listing';

 const link = listing.appendChild(document.createElement('a'));
 link.href = '#';
 link.className = 'title';
 link.id = `link-${store.properties.id}`;
 link.innerHTML = store.properties.name;

 const details = listing.appendChild(document.createElement('div'));
 details.innerHTML = `
 ${store.properties.address}

 ${store.properties.phone || ''}

 `;

 // Handle listing click
 link.addEventListener('click', (e) => {
 e.preventDefault();
 flyToStore(store);
 createPopup(store);
 highlightListing(store.properties.id);
 });
 });
}

function flyToStore(store) {
 map.flyTo({
 center: store.geometry.coordinates,
 zoom: 15,
 duration: 1000
 });
}

function createPopup(store) {
 const popups = document.getElementsByClassName('mapboxgl-popup');
 // Remove existing popups
 if (popups[0]) popups[0].remove();

 new mapboxgl.Popup({ closeOnClick: true })
 .setLngLat(store.geometry.coordinates)
 .setHTML(
 `

### ${store.properties.name}

 ${store.properties.address}

 ${store.properties.phone}

 ${store.properties.website ? ` Visit Website ` : ''}`
 )
 .addTo(map);
}

// IMPORTANT: highlightListing MUST include scrollIntoView — without it,
// selecting a marker on the map won't scroll the sidebar to the listing.
function highlightListing(id) {
 // Remove existing highlights
 const activeItem = document.getElementsByClassName('active');
 if (activeItem[0]) {
 activeItem[0].classList.remove('active');
 }

 // Add highlight to selected listing
 const listing = document.getElementById(`listing-${id}`);
 listing.classList.add('active');

 // Scroll the selected listing into view (critical UX requirement)
 listing.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

// Build the list on load
map.on('load', () => {
 buildLocationList(stores);
});

Reference Files

Load these references for additional patterns as needed:

Reference File Contents HTML Markers & Clustering references/markers.md HTML Markers (< 100 locations), Clustering (> 1000 locations) Search & Filter references/search-filter.md Text search, category filter Geolocation & Directions references/geolocation-directions.md User location, distance calculation, route directions Styling & Layout references/styling-layout.md Full HTML/CSS layout, custom marker CSS Performance & A11y references/optimization-a11y.md Debounced search, data management, error handling, accessibility Variations & React references/variations-react.md Mobile-first, fullscreen, map-only, React implementation

Resources