mapbox-store-locator-patterns▌
mapbox/mapbox-agent-skills · updated Apr 8, 2026
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.
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:
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:
{
"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 locationname- Display nameaddress- Full address for display and geocodingcoordinates-[longitude, latitude]formatcategory- For filtering (retail, restaurant, office, etc.)- Custom properties as needed (hours, phone, website, etc.)
Basic Store Locator Implementation
Step 1: Initialize Map and Data
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.
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
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 = `
<p>${store.properties.address}</p>
<p>${store.properties.phone || ''}</p>
`;
// 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(
`<h3>${store.properties.name}</h3>
<p>${store.properties.address}</p>
<p>${store.properties.phone}</p>
${store.properties.website ? `<a href="${store.properties.website}" target="_blank">Visit Website</a>` : ''}`
)
.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
- Turf.js - Spatial analysis library (recommended for distance calculations)
- Mapbox GL JS API
- Interactions API Guide
- GeoJSON Specification
- Directions API
- Store Locator Tutorial
Discussion
Product Hunt–style comments (not star reviews)- No comments yet — start the thread.
Ratings
4.8★★★★★27 reviews- ★★★★★Xiao Thomas· Dec 20, 2024
Registry listing for mapbox-store-locator-patterns matched our evaluation — installs cleanly and behaves as described in the markdown.
- ★★★★★Pratham Ware· Dec 12, 2024
Useful defaults in mapbox-store-locator-patterns — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Yash Thakker· Nov 3, 2024
mapbox-store-locator-patterns is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Dhruvi Jain· Oct 22, 2024
Keeps context tight: mapbox-store-locator-patterns is the kind of skill you can hand to a new teammate without a long onboarding doc.
- ★★★★★Amina Robinson· Sep 13, 2024
mapbox-store-locator-patterns has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Zaid Kim· Sep 1, 2024
Solid pick for teams standardizing on skills: mapbox-store-locator-patterns is focused, and the summary matches what you get after install.
- ★★★★★Harper Jackson· Aug 20, 2024
mapbox-store-locator-patterns has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Naina Nasser· Aug 4, 2024
Solid pick for teams standardizing on skills: mapbox-store-locator-patterns is focused, and the summary matches what you get after install.
- ★★★★★Harper Thompson· Jul 7, 2024
mapbox-store-locator-patterns fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Advait Jackson· Jun 26, 2024
We added mapbox-store-locator-patterns from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
showing 1-10 of 27