> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/ja/llms.txt)

# Build a store locator using Mapbox GL JS

This tutorial will walk you through how to create a store locator map using Mapbox GL JS. You'll be able to browse all the store locations from a sidebar and click on a [`Marker`](https://docs.mapbox.com/help/ja/glossary/marker/) for a specific store to view more information in the sidebar.

By the end of this tutorial you'll have completed the following:

-   Integrated Mapbox GL JS
-   Imported GeoJSON data for the store locations
-   Created custom interactive markers
-   Built a sidebar with store listings
-   Implemented interactivity across the map, markers and sidebar

You'll use the locations of [a popular lunch spot](http://sweetgreen.com/locations) in the Washington, D.C. area as example data, but the concepts apply to any business with multiple locations.

See a video of the finished product below or checkout the [finished app here](https://docs.mapbox.com/help/ja/help/ja/demos/building-a-store-locator/final/index.html).

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--building-a-store-locator-react-finished-demo-a402b6d82ebedd5626bf949d38138255.mp4).

> **Related content (tutorial): [Build a store locator in a frontend framework](https://docs.mapbox.com/help/ja/tutorials/building-a-store-locator-react)**
> 
> This tutorial uses standalone JavaScript, HTML and CSS to build the application. If you'd like to build a store locator in a framework, follow our **Build a store locator with React & Mapbox GL JS** tutorial.

## Prerequisites

There are a few resources you'll need before getting started:

-   **Familiarity with React development**: Intermediate experience with JSX, HTML, CSS, and JavaScript.
-   **Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) for free or [log in](https://console.mapbox.com/) if you already have an account.
-   **A Code editor**: A program like [Visual Studio Code](https://code.visualstudio.com/).

> **Related content (related): [Dive Deeper: Web Apps](https://docs.mapbox.com/help/ja/dive-deeper/web-apps/)**
> 
> This guide shows you how to use **Mapbox GL JS** to build an interactive web map. If you're new to Mapbox GL JS, you might want to read the guide on Mapbox web applications first.

## Add structure

In this section, you'll setup the files for the project and build the initial layout with HTML and CSS.

### Step 1: Create `index.html`

In your project folder, create an `index.html` file and copy the following code into the new file:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mapbox - Store Locator Demo</title>
    
    <!-- Mapbox GL JS -->
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.css" rel="stylesheet"/>
    
    <!-- CSS -->
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <!-- Sidebar -->
    <div class="sidebar">
        <h2 id="store-count" class="sidebar-title">Stores nearby: 0</h2>
        <div id="listings" class="listings"></div>
    </div>
    
    <!-- Map -->
    <div id="map" class="map"></div>

    <!-- Javscript in next steps will go here -->
    <script>
    </script>

</body>
</html>
```

In the HTML code above, you've imported the **Mapbox GL JS**'s JavaScript and CSS in the `<head>` as well as imported a CSS file, that you will define next. In the `<body>` there is a div with a class of `sidebar` to define the structure for the sidebar and a div with an id of `map` which will serve to contain the Mapbox map. Lastly there is an empty `<script>` tag which will house your JavaScript for the rest of the tutorial.

### Step 2: Create `styles.css`

Next, create a new file called `styles.css` and paste in the following CSS styles. The `index.html` file above includes an import for `./styles.css`, so make sure it is in the same directory as the HTML file.

```css
body {
    margin: 0;
    padding: 0;
    height: 100vh;
    overflow: hidden;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Layout */
.sidebar {
    position: absolute;
    width: 25%;
    height: 100%;
    top: 0;
    left: 0;
    padding: 1rem;
    overflow-y: scroll;
    background-color: #f4f3e7;
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
    z-index: 10;
}

.map {
    position: absolute;
    left: 25%;
    width: 75%;
    top: 0;
    bottom: 0;
    height: 100%;
}
```

### Step 3: Test in browser

Save your work and load the page in your browser. You will see a sidebar and an empty div that will hold the map:

![Empty Store locator layout](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--building-a-store-locator--empty-layout.04aaeeb.480.png)

## Initialize the map

Now that you have the structure of the page, you will initialize the map with Mapbox GL JS.

Copy the JavaScript code snippet below and paste it between the empty `<script></script>` tags at the bottom of `index.html`.

```js
  // Mapbox access token
    // Global state
    let map;

    /**
     * Initialize the map
     */
    function initMap() {
        map = new mapboxgl.Map({
            accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN ',
            container: 'map',
            center: [-77.03915, 38.90025],
            zoom: 12.5,
            config: {
              basemap: { theme: 'faded'}
            } 
        });

        map.on('load', () => {
            console.log('Map loaded');
        });
    }

    initMap();
```

If you are not logged into documentation, be sure to replace `YOUR_MAPBOX_ACCESS_TOKEN` with your access token from your [developer console](https://console.mapbox.com). If you are logged in, the code snippet will automatically include your access token into the code snippet.

The code above creates a new [`Map`](https://docs.mapbox.com/mapbox-gl-js/api/map/) object using `new mapboxgl.Map()` and stores it in a constant called `map`. The Mapbox GL JS map requires several options:

-   `container`: the `id` of the `<div>` element on the page where the map should live. In this case, the `id` for the `<div>` is `'map'`.
-   `center`: the initial centerpoint of the map in `[longitude, latitude]` format.
-   `zoom`: the initial zoom level of the map.
-   `config` (optional): The `config` object is not required but it is used in this tutorial to change the [Mapbox Standard Style](https://docs.mapbox.com/map-styles/standard/guides/) `theme` preset to `faded` giving the map style a more subdued color palette.

> **Related content (playground): [Mapbox Standard Style Playground](https://docs.mapbox.com/playground/standard-style/)**
> 
> Explore the customizable presets and options available in **Mapbox Standard Style** in the Mapbox Standard Style Playground.

Save your work, then reload the page in your browser. You will see the map loaded on the right side of the page.

![Layout with map](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--building-a-store-locator--map-only.c99c3e3.480.png)

## Load the store data

Now that the map is loading, the next step is to add the store data as a [source](https://docs.mapbox.com/mapbox-gl-js/api/sources/). To add a source to the map, your code needs to access geospatial data. [GeoJSON](https://docs.mapbox.com/help/ja/glossary/geojson/) is a format for encoding geospatial data and in the code block below you can see the `features` array which contains a `feature` object for each of the store locations. Each `feature` contains a `geometry` type of `point` with coordinates and the `properties` object which includes the property data like `name`, `address`, `phone` for each location.

### Step 1: Create `store.js`

Create a new file in your project folder called `stores.js` and copy and paste the code from the code block below into the file.

Title: `stores.js`

```javascript
const stores = {
    'type': 'FeatureCollection',
    'features': [
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.049766, 38.900772]
        },
        "properties": {
          "name": "Foggy Bottom",
          "phoneFormatted": "(202) 507-8357",
          "phone": "2025078357",
          "address": "2221 I St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20037",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.043929, 38.910525]
        },
        "properties": {
          "name": "Dupont",
          "phoneFormatted": "(202) 387-9338",
          "phone": "2023879338",
          "address": "1512 Connecticut Ave NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20036",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.002583742142, 38.887041080933]
        },
        "properties": {
          "name": "Capitol Hill",
          "phoneFormatted": "(202) 547-9338",
          "phone": "2025479338",
          "address": "221 Pennsylvania Ave SE",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20003",
          "state": "D.C."
        }
      },      
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.031333, 38.919315]
        },
        "properties": {
          "name": "14th + W",
          "phoneFormatted": "(215) 386-1365",
          "phone": "2025062956",
          "address": "1325 W St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20009",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.039751, 38.901223 ]
        },
        "properties": {
          "name": "Farragut Square",
          "phoneFormatted": "(202) 506-3079",
          "phone": "2025063079",
          "address": "888 17th St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20006",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.063121, 38.9037889]
        },
        "properties": {
          "name": "Georgetown",
          "phoneFormatted": "(202) 838-4300",
          "phone": "2028384300",
          "address": "1044 Wisconsin Ave NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20007",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.033967, 38.909834]
        },
        "properties": {
          "name": "Logan Circle",
          "phoneFormatted": "(202) 234-7336",
          "phone": "2022347336",
          "address": "1461 P St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20005",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.020081, 38.901820 ]
        },
        "properties": {
          "name": "Mount Vernon",
          "phoneFormatted": "(202) 793-7300 ",
          "phone": "2027937300 ",
          "address": "601 Massachusetts Avenue Northwest suite 110",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20001",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [ -77.000300, 38.874972 ]
        },
        "properties": {
          "name": "Navy Yard",
          "phoneFormatted": "(202) 554-7336 ",
          "phone": "2025547336 ",
          "address": " 1212 4th St SE",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20003",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.021372,  38.895985 ]
        },
        "properties": {
          "name": "Penn Quarter",
          "phoneFormatted": "(202) 804-2250 ",
          "phone": "2028042250 ",
          "address": "624 E. Street, NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20004",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-76.999066, 38.909368 ]
        },
        "properties": {
          "name": "Union Market",
          "phoneFormatted": "(202) 891-5954   ",
          "phone": "2028915954   ",
          "address": "1304 4th Street Northeast",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20002",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.049349, 38.905231 ]
        },
        "properties": {
          "name": "West End",
          "phoneFormatted": "(202) 629-2100   ",
          "phone": "2026292100  ",
          "address": "2238 M St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20037",
          "state": "D.C."
        }
      }
    ]
  };
```

### Step 2: Import `store.js` into your `index.html`

Now that you have added your `stores.js` file, it must be imported into `index.html`. Update your `index.html` file with the highlighted snippet below, importing the `stores` object into `index.html` before the end of the `<head>`.

```html
<!doctype html>
<html lang="en">
  <head>
    ...
    // highlight-start
    <!-- Store Data -->
    <script src="stores.js"></script>
    // highlight-end
  </head>
  ...
```

### Step 3: Add circle layer with your data

Now you can add a layer that contains this data and describes how it should be rendered. Add the data to your map once the map loads using the Mapbox GL JS [`addLayer()` method](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#addlayer). Create a new [circle layer](https://docs.mapbox.com/style-spec/reference/layers/#circle), and specify `stores` as a GeoJSON data source. Inside your `map.on('load'){}` call back, remove the `console.log` and paste in the `map.addLayer()` method highlighted below.

```js
...
map.on('load', () => {
  //highlight-start
  map.addLayer({
    id: 'locations',
    type: 'circle',
    source: {
      type: 'geojson',
      data: stores   /* References the imported data source from stores.js */
    }
  });
  // higlight-end
});
...
```

### Step 4: Test in browser

Save your work, and reload the page in your browser. You will see a circle at each store location listed in the GeoJSON.

![GeoJSON rendered with circle layer](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--building-a-store-locator--store-circle-layer.9360bba.480.png)

> **Note: Why import GeoJSON as JavaScript?**
> 
> In the code above, GeoJSON data is imported not as a direct GeoJSON file, but as a JavaScript object which is then passed to the application. This approach is used in the tutorial as the `store.js` file is the source data for both the Markers on the map, and the store listing in the sidebar (outside of the map).
> 
> If you had to use a GeoJSON file, you could make a `fetch()` request for the file on page load and parse it to JSON and pass the response to the map & sidebar.

## Add custom markers

This section will walk you through how to replace the existing [circle layer](https://docs.mapbox.com/style-spec/reference/layers/#circle) with custom markers. Unlike the circle layer, which is in the map, [`mapboxgl.Marker()`](https://docs.mapbox.com/mapbox-gl-js/api/markers/#marker) objects are HTML DOM elements that sit on top of the map and can be styled with CSS.

### Step 1: Create the `addMarkers()` function

To add the new markers to the map, you will make a few updates to `index.html`.

-   Declare a top level `markers` array that you'll use to store your markers in. This will help in the future if you want to add new functionality to this app by being able to track and update the markers.
-   Create an `addMarkers` function to iterate through all stores and add the new [Marker](https://docs.mapbox.com/mapbox-gl-js/api/markers/#marker) object to the map at each location.
-   Remove the existing circle layer by deleting the `.addLayer()` function from within your `map.on('load')` call back
-   Call your `addMarkers` function inside the `map.on('load')` call back.

Review the code snippet below and update your `index.html` to match.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox - Store Locator Demo</title>

    <!-- Mapbox GL JS -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js"></script>

    <!-- Custom CSS -->
    <link rel="stylesheet" href="styles.css" />

    <!-- Store Data -->
    <script src="stores.js"></script>
  </head>
  <body>
    <!-- Sidebar -->
    <div class="sidebar">
      <h2 id="store-count" class="sidebar-title">Stores nearby: 0</h2>
      <div id="listings" class="listings"></div>
    </div>

    <!-- Map -->
    <div id="map" class="map"></div>

    <script>
      // Mapbox access token
      // Global state
      let map;
      const markers = [];

      /**
       * Initialize the map
       */
      function initMap() {
        map = new mapboxgl.Map({
          accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
          container: 'map',
          center: [-77.03915, 38.90025],
          zoom: 12.5,
          config: {
            basemap: { theme: 'faded' }
          }
        });

        map.on('load', () => {
          // remove addLayer() and replace with addMarkers()
          addMarkers();
        });
      }

      /**
       * Add markers to the map for each store
       */
      function addMarkers() {
        // Clear existing markers
        markers.forEach((marker) => marker.remove());

        stores.features.forEach((store, index) => {
          // Create marker element
          const el = document.createElement('div');
          el.id = `marker-${index}`;
          el.className = 'marker';
          el.dataset.storeIndex = index;

          // Create Mapbox marker with proper anchoring
          const marker = new mapboxgl.Marker({
            element: el,
            anchor: 'bottom'
          })
            .setLngLat(store.geometry.coordinates)
            .addTo(map);

          markers.push(marker);

          // Add click event
          el.addEventListener('click', (e) => {
            e.stopPropagation();
            console.log('Store: ', store.properties.name);
          });
        });
      }

      initMap();
    </script>
  </body>
</html>
```

> **Note: Overview of addMarkers() key functionality.**
> 
> A clean up function iterates over the `markers` array and removes each Marker from the map to make sure synchronized state between the global `markers` array and markers on the map.
> 
> Next, the code creates a new marker div named `el` for each item in the `stores.features` array. The div is then passed into a new `mapboxgl.Marker()` and the location is defined with the `.setLngLat()` method. The new Marker object is then added to the map and pushed into the `markers` array.
> 
> Lastly a click `eventListener` is added to the marker which logs the store name to the console when a marker is clicked.

Now that your markers are added to the map, you can add the SVG assets to render at each location.

### Step 2: Add SVG assets and CSS.

Before you can see the Markers on the map you'll add custom icons to the `Marker` objects using CSS. Download the 2 icons in the `markers.zip` file available here.

[Download Marker SVG's](https://docs.mapbox.com/help/ja/help/img/store-locator-react/markers.zip)

-   Unzip the folder into your project directory, which contains 2 SVG files.
-   Add a new class to the bottom of `styles.css` called `.marker` which references the SVGs inside the `/markers` folder.

```css
...
/* Markers */
.marker {
  background-image: url('./markers/sg-marker.svg');
  background-size: contain;
  background-repeat: no-repeat;
  width: 37px;
  height: 40px;
  cursor: pointer;
}

.marker:hover {
  opacity: 0.8;
}

.marker.active {
  background-image: url('./markers/sg-marker-selected.svg');
}
```

Now, when you reload the app you should see the Mapbox `Marker`s displayed on top of the map with the custom SVG's, and because of click event you can click a `Marker` element and see the name of that store logged to the console.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--bulding-a-store-locator--markers-6134d806b3700eada2c922b4a07c7299.mp4).

## Build store listing

Now that the points are on your map, it's time to build the store location listing by iterating through the GeoJSON and creating a list of location dynamically. This means that if you need to add a location then you *only* need to update the GeoJSON.

### Step 1: Create the `buildLocationList` function.

Create a new function, `buildLocationList`, to iterate through the store locations and add each one to the sidebar listing. This function updates the `h2` of the sidebar to list the number of stores, it selects the `listings` DOM node and iterates over the `stores.features` array creating a div for each store with a `store-item` class and a unique `id` and then creates some markup to house the store `name`,`address` and `phoneFormatted` properties and appends each new store to the `listings` node.

Add the new function at the bottom of your `<script>` tag before the final `initMap()` call and call your new function `buildLocationList()` inside the `map.on('load')` call back after your `buildMarkers()` function.

Explore the code snippet below, updating your `index.html` file with the new functionality.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox - Store Locator Demo</title>

    <!-- Mapbox GL JS -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js"></script>

    <!-- Custom CSS -->
    <link rel="stylesheet" href="styles.css" />

    <!-- Store Data -->
    <script src="stores.js"></script>
  </head>
  <body>
    <!-- Sidebar -->
    <div class="sidebar">
      <h2 id="store-count" class="sidebar-title">Stores nearby: 0</h2>
      <div id="listings" class="listings"></div>
    </div>

    <!-- Map -->
    <div id="map" class="map"></div>

    <script>
      // Mapbox access token
      // Global state
      let map;
      const markers = [];

      /**
       * Initialize the map
       */
      function initMap() {
        map = new mapboxgl.Map({
          accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
          container: 'map',
          center: [-77.03915, 38.90025],
          zoom: 12.5,
          config: {
            basemap: { theme: 'faded' }
          }
        });

        map.on('load', () => {
          addMarkers();
          buildLocationList(); // Add buildLocationList()
        });
      }

      /**
       * Add markers to the map for each store
       */
      function addMarkers() {
        // Clear existing markers
        markers.forEach((marker) => marker.remove());

        stores.features.forEach((store, index) => {
          // Create marker element
          const el = document.createElement('div');
          el.id = `marker-${index}`;
          el.className = 'marker';
          el.dataset.storeIndex = index;

          // Create Mapbox marker with proper anchoring
          const marker = new mapboxgl.Marker({
            element: el,
            anchor: 'bottom'
          })
            .setLngLat(store.geometry.coordinates)
            .addTo(map);

          markers.push(marker);

          // Add click event
          el.addEventListener('click', (e) => {
            e.stopPropagation();
            console.log('Store: ', store.properties.name);
          });
        });
      }

      /**
       * Build the sidebar listing of stores
       */
      function buildLocationList() {
        const listings = document.getElementById('listings');
        const storeCount = document.getElementById('store-count');

        // Update store count based on GeoJSON features length
        storeCount.textContent = `Stores nearby: ${stores.features.length}`;

        // Clear existing listings
        listings.innerHTML = '';

        stores.features.forEach((store, index) => {
          // Create listing element with store-item CSS class
          const listing = document.createElement('div');
          listing.id = `listing-${index}`;
          listing.className = 'store-item';
          listing.dataset.storeIndex = index;
          // Set the content of each listing with the name, address and phone of each store
          listing.innerHTML = `
                      <h4 class="store-name">${store.properties.name}</h4>
                      <div class="store-details">
                          <div>
                              <span class="label">Address: </span>${store.properties.address}
                          </div>
                          <div>
                              <span class="label">Phone: </span>${store.properties.phoneFormatted}
                          </div>
                      </div>
                  `;

          listings.appendChild(listing);
        });
      }

      initMap();
    </script>
  </body>
</html>
```

### Step 2: Style the sidebar with CSS.

Next, add the following to your CSS to style the new layout updates for the sidebar:

```css
...
/* Sidebar title */
.sidebar-title {
  color: #00473c;
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0 0 1.5rem 0;
}

/* Store listings container */

.store-item {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 1.5rem 0;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  border: 1px solid #00473c;
  border-radius: 0.5rem;
  transition: background-color 0.3s ease;
  cursor: pointer;
  padding: 1rem;
  scroll-margin: 1.25rem 0 1.25rem 0;
  background-color: transparent;
}

.store-item:hover {
  background-color: white;
}

.store-item.active {
  background-color: white;
}

.store-name {
  margin-bottom: 0.5rem;
  color: #00473c;
  font-size: 1.25rem;
  font-weight: 600;
  margin-top: 0;
}

.store-details {
  color: #00473c;
  line-height: 1.5;
  font-weight: 300;
}

.store-details .label {
  font-weight: 700;
  font-size: 0.875rem;
}

.store-details > div {
  margin-bottom: 0.25rem;
}
```

**Step 3**: Test in browser.

Save your work and reload the page in the browser. The store listings will populate the sidebar on the left side of the page.

![Finished layout with Sidebar & Markers](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--building-a-store-locator--complete-layout.d8d7b85.480.png)

Next you will work on adding interactivity tying the sidebar, map and markers together into a cohesive app experience.

## Make the map interactive

When a user clicks a link in the sidebar or on a marker on the map, you want three things to happen:

-   **The map should fly to the associated store location.**
-   **The marker for that location should change to a 'selected' state.**
-   **The listing should be highlighted in the sidebar.**

This will require more code, but you can do it!

### Step 1: Create the `flyToStore` function

First create the `flyToStore` function to use the Mapbox GL JS [`flyTo`](https://docs.mapbox.com/mapbox-gl-js/api/map/#map#flyto) method. This will center the map on the correct store location and zoom in.

Declare this function at the bottom of the `<script>` tag above `initMap()`.

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

### Step 2: Create the `selectStore` event handler

`selectStore` is an event handler which updates the map markers and the sidebar listing with the active store.

When `selectStore` runs, it gets any active listing nodes with `document.querySelectorAll('.store-item.active')` and removes the `active` class. Then, using the `index` it sets a new `active` class on the selected listing and calls the web API [`scrollIntoView`](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) to scroll the selected store into view in the sidebar container. This makes sure that when a marker on the map is clicked and the listing in the sidebar is out of view, that the sidebar will scroll that selected listing into view.

A similar process repeats on the map markers by removing any existing `active` state, adding a new `active` state for the selected marker using the `index` - which changes the marker icon with a different CSS class, and finally it then calls `flyToStore()` on the marker to move the map to the location.

Declare this function between your `flyToStore` function and above `initMap()` to add this functionality to your project.

```javascript
/**
* Handle store selection from either marker or sidebar
*/
function selectStore(store, index) {
  console.log('Store selected:', store.properties.name);

  // Update sidebar - remove previous active state
  const activeItems = document.querySelectorAll('.store-item.active');
  activeItems.forEach((item) => item.classList.remove('active'));

  // Add active state to selected item
  const selectedListing = document.getElementById(`listing-${index}`);
  if (selectedListing) {
      selectedListing.classList.add('active');
      selectedListing.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  // Update markers - remove previous active state
  const activeMarkers = document.querySelectorAll('.marker.active');
  activeMarkers.forEach((marker) => marker.classList.remove('active'));

  // Add active state to selected marker
  const selectedMarker = document.getElementById(`marker-${index}`);
  if (selectedMarker) {
      selectedMarker.classList.add('active');
  }

  // Fly to the selected store
  flyToStore(store);
}
```

Now your code should match the code below:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox - Store Locator Demo</title>

    <!-- Mapbox GL JS -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js"></script>

    <!-- Custom CSS -->
    <link rel="stylesheet" href="styles.css" />

    <!-- Store Data -->
    <script src="stores.js"></script>
  </head>
  <body>
    <!-- Sidebar -->
    <div class="sidebar">
      <h2 id="store-count" class="sidebar-title">Stores nearby: 0</h2>
      <div id="listings" class="listings"></div>
    </div>

    <!-- Map -->
    <div id="map" class="map"></div>

    <script>
      // Mapbox access token
      // Global state
      let map;
      const markers = [];

      /**
       * Initialize the map
       */
      function initMap() {
        map = new mapboxgl.Map({
          accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
          container: 'map',
          center: [-77.03915, 38.90025],
          zoom: 12.5,
          config: {
            basemap: { theme: 'faded' }
          }
        });

        map.on('load', () => {
          addMarkers();
          buildLocationList(); // Add buildLocationList()
        });
      }

      /**
       * Add markers to the map for each store
       */
      function addMarkers() {
        // Clear existing markers
        markers.forEach((marker) => marker.remove());

        stores.features.forEach((store, index) => {
          // Create marker element
          const el = document.createElement('div');
          el.id = `marker-${index}`;
          el.className = 'marker';
          el.dataset.storeIndex = index;

          // Create Mapbox marker with proper anchoring
          const marker = new mapboxgl.Marker({
            element: el,
            anchor: 'bottom'
          })
            .setLngLat(store.geometry.coordinates)
            .addTo(map);

          markers.push(marker);

          // Modify click event for Markers
          el.addEventListener('click', (e) => {
            e.stopPropagation();
            selectStore(store, index); // remove console.log and add selectStore()
          });
        });
      }

      /**
       * Build the sidebar listing of stores
       */
      function buildLocationList() {
        const listings = document.getElementById('listings');
        const storeCount = document.getElementById('store-count');

        // Update store count based on GeoJSON features length
        storeCount.textContent = `Stores nearby: ${stores.features.length}`;

        // Clear existing listings
        listings.innerHTML = '';

        stores.features.forEach((store, index) => {
          // Create listing element with store-item CSS class
          const listing = document.createElement('div');
          listing.id = `listing-${index}`;
          listing.className = 'store-item';
          listing.dataset.storeIndex = index;
          // Set the content of each listing with the name, address and phone of each store
          listing.innerHTML = `
                      <h4 class="store-name">${store.properties.name}</h4>
                      <div class="store-details">
                          <div>
                              <span class="label">Address: </span>${store.properties.address}
                          </div>
                          <div>
                              <span class="label">Phone: </span>${store.properties.phoneFormatted}
                          </div>
                      </div>
                  `;
          // Add click event for sidebar listing
          listing.addEventListener('click', () => {
            selectStore(store, index);
          });
          listings.appendChild(listing);
        });
      }
      /**
       * Fly to a store location on the map
       */
      function flyToStore(store) {
        map.flyTo({
          center: store.geometry.coordinates,
          zoom: 13,
          duration: 1000
        });
      }

      /**
       * Handle store selection from either marker or sidebar
       */
      function selectStore(store, index) {
        console.log('Store selected:', store.properties.name);

        // Update sidebar - remove previous active state
        const activeItems = document.querySelectorAll('.store-item.active');
        activeItems.forEach((item) => item.classList.remove('active'));

        // Add active state to selected item
        const selectedListing = document.getElementById(`listing-${index}`);
        if (selectedListing) {
          selectedListing.classList.add('active');
          selectedListing.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest'
          });
        }

        // Update markers - remove previous active state
        const activeMarkers = document.querySelectorAll('.marker.active');
        activeMarkers.forEach((marker) => marker.classList.remove('active'));

        // Add active state to selected marker
        const selectedMarker = document.getElementById(`marker-${index}`);
        if (selectedMarker) {
          selectedMarker.classList.add('active');
        }

        // Fly to the selected store
        flyToStore(store);
      }

      initMap();
    </script>
  </body>
</html>
```

Now that you've added the functionality, you'll reference this functions during a user interaction.

### Step 2: Add event listeners

Now that you've defined these two functions, you want them to fire when a user clicks on a store in the sidebar listing or when a user clicks a marker on the map. To do this, you will add a new event listener to the `buildLocationList()` function and update the event listener in the `addMarkers()` function.

These functions will be fired both when a user clicks on a link in the sidebar listing and when a user clicks on a store marker in the map. Review the updates to `index.html` which are highlighted in the code block below and update your functions to include the updated event listeners.

Title: `index.html`

```js
...
function addMarkers() {
  // Clear existing markers
  markers.forEach((marker) => marker.remove());

  stores.features.forEach((store, index) => {
    // Create marker element
    const el = document.createElement('div');
    el.id = `marker-${index}`;
    el.className = 'marker';
    el.dataset.storeIndex = index;

    // Create Mapbox marker with proper anchoring
    const marker = new mapboxgl.Marker({ element: el, anchor: 'bottom' })
      .setLngLat(store.geometry.coordinates)
      .addTo(map);

    markers.push(marker);

    // Modify click event for markers
    el.addEventListener('click', (e) => {
      e.stopPropagation();
      // highlight-start
      selectStore(store, index);
      // remove console.log and add selectStore()
      // highlight-end
    });
  });
}

/**
 * Build the sidebar listing of stores
 */
function buildLocationList() {
  const listings = document.getElementById('listings');
  const storeCount = document.getElementById('store-count');

  // Update store count based on GeoJSON features length
  storeCount.textContent = `Stores nearby: ${stores.features.length}`;

  // Clear existing listings
  listings.innerHTML = '';

  stores.features.forEach((store, index) => {
    // Create listing element with store-item CSS class
    const listing = document.createElement('div');
    listing.id = `listing-${index}`;
    listing.className = 'store-item';
    listing.dataset.storeIndex = index;

    // Set the content of each listing with the name, address and phone of each store
    listing.innerHTML = `
      <h4 class="store-name">${store.properties.name}</h4>
      <div class="store-details">
        <div><span class="label">Address: </span>${store.properties.address}</div>
        <div>
          <span class="label">Phone: </span>${store.properties.phoneFormatted}
        </div>
      </div>
    `;

    // highlight-start
    // Add click event for sidebar listing
    listing.addEventListener('click', () => {
      selectStore(store, index);
    });
    // highlight-end

    listings.appendChild(listing);
  });
}
...
```

Save your changes, and reload the page. Now when you click on one of the locations, the map will fly to that store and highlight the correct store listing in the sidebar. If you click on one of the listings in the sidebar, the map will also fly to the correct location.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--building-a-store-locator--scrollIntoView-a0ce94fe4556c0c285dc9be947bf2436.mp4).

## Final product

Below you can see a final product video or view a full screen demo of the [final product here](https://docs.mapbox.com/help/ja/help/ja/demos/building-a-store-locator/final/index.html).

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--building-a-store-locator--final-product-53bc18621c4622dc97a561059e1a0bb4.mp4).

### Final Code

Below you'll find the final code snippets for the 3 main files: `index.html`, `styles.css` and `stores.js`

Title: `index.html`

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mapbox - Store Locator Demo</title>

    <!-- Mapbox GL JS -->
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js"></script>

    <!-- Custom CSS -->
    <link rel="stylesheet" href="styles.css" />

    <!-- Store Data -->
    <script src="stores.js"></script>
  </head>
  <body>
    <!-- Sidebar -->
    <div class="sidebar">
      <h2 id="store-count" class="sidebar-title">Stores nearby: 0</h2>
      <div id="listings" class="listings"></div>
    </div>

    <!-- Map -->
    <div id="map" class="map"></div>

    <script>
      // Mapbox access token
      // Global state
      let map;
      const markers = [];

      /**
       * Initialize the map
       */
      function initMap() {
        map = new mapboxgl.Map({
          accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',
          container: 'map',
          center: [-77.03915, 38.90025],
          zoom: 12.5,
          config: {
            basemap: { theme: 'faded' }
          }
        });

        map.on('load', () => {
          addMarkers();
          buildLocationList(); // Add buildLocationList()
        });
      }

      /**
       * Add markers to the map for each store
       */
      function addMarkers() {
        // Clear existing markers
        markers.forEach((marker) => marker.remove());

        stores.features.forEach((store, index) => {
          // Create marker element
          const el = document.createElement('div');
          el.id = `marker-${index}`;
          el.className = 'marker';
          el.dataset.storeIndex = index;

          // Create Mapbox marker with proper anchoring
          const marker = new mapboxgl.Marker({
            element: el,
            anchor: 'bottom'
          })
            .setLngLat(store.geometry.coordinates)
            .addTo(map);

          markers.push(marker);

          // Modify click event for Markers
          el.addEventListener('click', (e) => {
            e.stopPropagation();
            selectStore(store, index); // remove console.log and add selectStore()
          });
        });
      }

      /**
       * Build the sidebar listing of stores
       */
      function buildLocationList() {
        const listings = document.getElementById('listings');
        const storeCount = document.getElementById('store-count');

        // Update store count based on GeoJSON features length
        storeCount.textContent = `Stores nearby: ${stores.features.length}`;

        // Clear existing listings
        listings.innerHTML = '';

        stores.features.forEach((store, index) => {
          // Create listing element with store-item CSS class
          const listing = document.createElement('div');
          listing.id = `listing-${index}`;
          listing.className = 'store-item';
          listing.dataset.storeIndex = index;
          // Set the content of each listing with the name, address and phone of each store
          listing.innerHTML = `
                      <h4 class="store-name">${store.properties.name}</h4>
                      <div class="store-details">
                          <div>
                              <span class="label">Address: </span>${store.properties.address}
                          </div>
                          <div>
                              <span class="label">Phone: </span>${store.properties.phoneFormatted}
                          </div>
                      </div>
                  `;
          // Add click event for sidebar listing
          listing.addEventListener('click', () => {
            selectStore(store, index);
          });
          listings.appendChild(listing);
        });
      }
      /**
       * Fly to a store location on the map
       */
      function flyToStore(store) {
        map.flyTo({
          center: store.geometry.coordinates,
          zoom: 13,
          duration: 1000
        });
      }

      /**
       * Handle store selection from either marker or sidebar
       */
      function selectStore(store, index) {
        console.log('Store selected:', store.properties.name);

        // Update sidebar - remove previous active state
        const activeItems = document.querySelectorAll('.store-item.active');
        activeItems.forEach((item) => item.classList.remove('active'));

        // Add active state to selected item
        const selectedListing = document.getElementById(`listing-${index}`);
        if (selectedListing) {
          selectedListing.classList.add('active');
          selectedListing.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest'
          });
        }

        // Update markers - remove previous active state
        const activeMarkers = document.querySelectorAll('.marker.active');
        activeMarkers.forEach((marker) => marker.classList.remove('active'));

        // Add active state to selected marker
        const selectedMarker = document.getElementById(`marker-${index}`);
        if (selectedMarker) {
          selectedMarker.classList.add('active');
        }

        // Fly to the selected store
        flyToStore(store);
      }

      initMap();
    </script>
  </body>
</html>
```

Title: `styles.css`

```css
/* Reset and base styles */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  height: 100vh;
  overflow: hidden;
  font-family:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Layout */
.sidebar {
  position: absolute;
  width: 25%;
  height: 100%;
  top: 0;
  left: 0;
  padding: 1rem;
  overflow-y: scroll;
  background-color: #f4f3e7;
  box-shadow: 0 25px 50px -12px rgb(0 0 0 / 25%);
  z-index: 10;
}

.map {
  position: absolute;
  left: 25%;
  width: 75%;
  top: 0;
  bottom: 0;
  height: 100%;
}

/* Sidebar title */
.sidebar-title {
  color: #00473c;
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0 0 1.5rem;
}

/* Store listings container */

.store-item {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 1.5rem 0;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%);
  border: 1px solid #00473c;
  border-radius: 0.5rem;
  transition: background-color 0.3s ease;
  cursor: pointer;
  padding: 1rem;
  scroll-margin: 1.25rem 0;
  background-color: transparent;
}

.store-item:hover {
  background-color: white;
}

.store-item.active {
  background-color: white;
}

.store-name {
  margin-bottom: 0.5rem;
  color: #00473c;
  font-size: 1.25rem;
  font-weight: 600;
  margin-top: 0;
}

.store-details {
  color: #00473c;
  line-height: 1.5;
  font-weight: 300;
}

.store-details .label {
  font-weight: 700;
  font-size: 0.875rem;
}

.store-details > div {
  margin-bottom: 0.25rem;
}

/* Markers */
.marker {
  background-image: url('./markers/sg-marker.svg');
  background-size: contain;
  background-repeat: no-repeat;
  width: 37px;
  height: 40px;
  cursor: pointer;
}

.marker:hover {
  opacity: 0.8;
}

.marker.active {
  background-image: url('./markers/sg-marker-selected.svg');
}
```

Title: `stores.js`

```js
const stores = {
    'type': 'FeatureCollection',
    'features': [
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.049766, 38.900772]
        },
        "properties": {
          "name": "Foggy Bottom",
          "phoneFormatted": "(202) 507-8357",
          "phone": "2025078357",
          "address": "2221 I St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20037",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.043929, 38.910525]
        },
        "properties": {
          "name": "Dupont",
          "phoneFormatted": "(202) 387-9338",
          "phone": "2023879338",
          "address": "1512 Connecticut Ave NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20036",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.002583742142, 38.887041080933]
        },
        "properties": {
          "name": "Capitol Hill",
          "phoneFormatted": "(202) 547-9338",
          "phone": "2025479338",
          "address": "221 Pennsylvania Ave SE",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20003",
          "state": "D.C."
        }
      },      
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.031333, 38.919315]
        },
        "properties": {
          "name": "14th + W",
          "phoneFormatted": "(215) 386-1365",
          "phone": "2025062956",
          "address": "1325 W St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20009",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.039751, 38.901223 ]
        },
        "properties": {
          "name": "Farragut Square",
          "phoneFormatted": "(202) 506-3079",
          "phone": "2025063079",
          "address": "888 17th St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20006",
          "state": "D.C."
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.063121, 38.9037889]
        },
        "properties": {
          "name": "Georgetown",
          "phoneFormatted": "(202) 838-4300",
          "phone": "2028384300",
          "address": "1044 Wisconsin Ave NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20007",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.033967, 38.909834]
        },
        "properties": {
          "name": "Logan Circle",
          "phoneFormatted": "(202) 234-7336",
          "phone": "2022347336",
          "address": "1461 P St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20005",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.020081, 38.901820 ]
        },
        "properties": {
          "name": "Mount Vernon",
          "phoneFormatted": "(202) 793-7300 ",
          "phone": "2027937300 ",
          "address": "601 Massachusetts Avenue Northwest suite 110",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20001",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [ -77.000300, 38.874972 ]
        },
        "properties": {
          "name": "Navy Yard",
          "phoneFormatted": "(202) 554-7336 ",
          "phone": "2025547336 ",
          "address": " 1212 4th St SE",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20003",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.021372,  38.895985 ]
        },
        "properties": {
          "name": "Penn Quarter",
          "phoneFormatted": "(202) 804-2250 ",
          "phone": "2028042250 ",
          "address": "624 E. Street, NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20004",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-76.999066, 38.909368 ]
        },
        "properties": {
          "name": "Union Market",
          "phoneFormatted": "(202) 891-5954   ",
          "phone": "2028915954   ",
          "address": "1304 4th Street Northeast",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20002",
          "state": "D.C."
        }
      },
    { 
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [-77.049349, 38.905231 ]
        },
        "properties": {
          "name": "West End",
          "phoneFormatted": "(202) 629-2100   ",
          "phone": "2026292100  ",
          "address": "2238 M St NW",
          "city": "Washington DC",
          "country": "United States",
          "postalCode": "20037",
          "state": "D.C."
        }
      }
    ]
  };
```

## Next steps

🎉 **Congratulations!** You've built a store locator powered by [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/). That was a lot of code! After following this guide, you have the tools you need to create your own store locator.

### What we covered

-   Integrated Mapbox GL JS
-   Imported GeoJSON data for the store locations
-   Created custom interactive markers
-   Built a sidebar with store listings
-   Implemented interactivity across the map, markers and sidebar

### Learn more

> **Related content (tutorial): [Building a Store locator with React and Typescript](https://docs.mapbox.com/help/ja/tutorials/building-a-store-locator-react/)**
> 
> Interested in building an application like this with React, Typescript & TailwindCSS? See the matching tutorial to cover the same functionality with a different stack.

> **Note (info): Demo Store Locator**
> 
> **Public tools & demos:** If you are interested in building a more robust store locator in React, we have built a store locator app that is available to start using in your own project. See the online [demo here](https://labs.mapbox.com/demo-store-locator/) and browse the source code in the [public tools and demos](https://github.com/mapbox/public-tools-and-demos/tree/main/projects/demo-store-locator) repository.