---
title: "Example: Pickup Locator"
description: "Build a production-shaped store locator with accessible selection, map fallbacks, popups, and a delivery zone."
canonical_url: "https://scripts.nuxt.com/scripts/leaflet/guides/pickup-locator"
last_updated: "2026-08-11T04:27:01.985Z"
---

This real-world pickup locator pairs a location list with the map. The list keeps addresses and opening hours available when tiles fail, and it gives keyboard users a direct way to choose a location.

It demonstrates reactive selection, accessible location buttons, explicit OpenStreetMap attribution, marker popups, error fallback content, and a GeoJSON delivery zone.

<code-group>
<leaflet-pickup-locator-demo label="Demo">



</leaflet-pickup-locator-demo>

```vue [Source]
<script setup lang="ts">
import type { Feature, Polygon } from 'geojson'
import type { LatLngExpression, LatLngTuple } from 'leaflet'

interface PickupLocation {
  id: string
  name: string
  address: string
  hours: string
  position: LatLngTuple
}

const locations: PickupLocation[] = [
  {
    id: 'flinders-lane',
    name: 'Flinders Lane',
    address: 'Centre Place, Melbourne VIC',
    hours: 'Open today until 6 pm',
    position: [-37.8164, 144.9656],
  },
  {
    id: 'queen-victoria-market',
    name: 'Queen Victoria Market',
    address: 'Queen Street, Melbourne VIC',
    hours: 'Open today until 3 pm',
    position: [-37.8076, 144.9568],
  },
  {
    id: 'southbank',
    name: 'Southbank',
    address: 'Southbank Promenade, Southbank VIC',
    hours: 'Open today until 8 pm',
    position: [-37.8202, 144.9655],
  },
]

const selectedId = ref(locations[0]!.id)
const center = ref<LatLngExpression>(locations[0]!.position)
const zoom = ref(14)

const deliveryZone: Feature<Polygon> = {
  type: 'Feature',
  properties: { name: 'Same-day delivery zone' },
  geometry: {
    type: 'Polygon',
    coordinates: [[
      [144.946, -37.802],
      [144.982, -37.802],
      [144.986, -37.828],
      [144.951, -37.832],
      [144.946, -37.802],
    ]],
  },
}

function selectLocation(location: PickupLocation) {
  selectedId.value = location.id
  center.value = location.position
  zoom.value = 16
}
</script>

<template>
  <div class="pickup-locator">
    <aside aria-labelledby="pickup-title">
      <h2 id="pickup-title">
        Pickup in Melbourne
      </h2>

      <ol>
        <li v-for="location in locations" :key="location.id">
          <button
            type="button"
            :aria-pressed="selectedId === location.id"
            @click="selectLocation(location)"
          >
            <strong>{{ location.name }}</strong>
            <span>{{ location.address }}</span>
            <small>{{ location.hours }}</small>
          </button>
        </li>
      </ol>
    </aside>

    <ScriptLeafletMap
      v-model:center="center"
      v-model:zoom="zoom"
      width="100%"
      :height="520"
      aria-label="Pickup locations and delivery zone in central Melbourne"
    >
      <template #error>
        <p role="alert">
          The map is unavailable. Choose a pickup location from the list.
        </p>
      </template>

      <ScriptLeafletTileLayer
        url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
        :options="{
          maxZoom: 19,
          attribution: '&copy; <a href=&quot;https://www.openstreetmap.org/copyright&quot;>OpenStreetMap contributors</a>',
        }"
      />

      <ScriptLeafletGeoJson
        :data="deliveryZone"
        :options="{
          style: {
            color: '#15803d',
            fillColor: '#86efac',
            fillOpacity: 0.18,
            weight: 2,
          },
        }"
      />

      <ScriptLeafletMarker
        v-for="location in locations"
        :key="location.id"
        :position="location.position"
        :alt="`${location.name} pickup location`"
        :title="location.name"
        @click="selectLocation(location)"
      >
        <ScriptLeafletPopup
          :open="selectedId === location.id"
          :options="{ minWidth: 180 }"
        >
          <strong>{{ location.name }}</strong><br>
          {{ location.address }}<br>
          {{ location.hours }}
        </ScriptLeafletPopup>
      </ScriptLeafletMarker>
    </ScriptLeafletMap>
  </div>
</template>
```

</code-group>

Use CSS Grid to place the list beside the map on wide screens and above it on small screens. Keep the list in the document even when the map is the main visual.

GeoJSON coordinates use `[longitude, latitude]`; Leaflet marker positions use `[latitude, longitude]`.
