---
title: "VMap"
description: "The core map component for rendering MapLibre GL maps"
canonical_url: "https://v-maplibre.geoql.in/components/map"
---
# VMap

> The core map component for rendering MapLibre GL maps

## Usage

```vue
<script setup lang="ts">
  import { VMap } from '@geoql/v-maplibre';
  import type { MapOptions } from 'maplibre-gl';

  const mapOptions: MapOptions = {
    style: 'https://demotiles.maplibre.org/style.json',
    center: [-74.5, 40],
    zoom: 9,
  };

  const handleMapLoad = (map) => {
    console.log('Map loaded!', map);
  };
</script>

<template>
  <VMap
    :options="mapOptions"
    @loaded="handleMapLoad"
    style="height: 500px"
  ></VMap>
</template>
```

## Props

### options

- **Type:** `MapOptions`
- **Required:** `true`
- **Default:** `{ container: 'map' }`

MapLibre GL map options. See [MapLibre GL documentation](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/) for all available options.

Common options include:

- `style`: URL to the map style or a style object
- `center`: Initial map center `[lng, lat]`
- `zoom`: Initial zoom level
- `bearing`: Initial bearing (rotation)
- `pitch`: Initial pitch (tilt)
- `minZoom`: Minimum zoom level
- `maxZoom`: Maximum zoom level
- `maxBounds`: Map bounds constraint

### supportPmtiles

- **Type:** `boolean`
- **Required:** `false`
- **Default:** `false`

Enable PMTiles protocol support. Set to `true` if you plan to use PMTiles layers.

```vue
<VMap :options="mapOptions" support-pmtiles></VMap>
```

### globalState

- **Type:** `Record<string, unknown>`
- **Required:** `false`
- **Default:** `undefined`

Reactive values for [`global-state`](https://maplibre.org/maplibre-style-spec/expressions/#global-state) expressions in the style. The object is the initial state (it overrides the defaults in the style's `state` property and takes precedence over `options.globalState`). After the map is created, each changed key calls `map.setGlobalStateProperty`, and a removed key resets to the style default. Nested changes (an array item, an object field) are detected.

```vue
<script setup lang="ts">
  import { ref } from 'vue';

  // The style filters a layer with ["global-state", "minPopulation"].
  const state = ref({ minPopulation: 100000 });
</script>

<template>
  <VMap :options="mapOptions" :global-state="state"></VMap>
  <input v-model.number="state.minPopulation" type="range" max="5000000" />
</template>
```

### dragRotate

- **Type:** `boolean | DragRotateOptions`
- **Required:** `false`
- **Default:** `undefined` (MapLibre default)

Reactive drag-to-rotate behaviour. `false` disables it and `true` uses the defaults. An object picks the pivot (MapLibre 6.15+):

- `{ around: 'pointer' }` turns and tilts around the point under the pointer, on the terrain where there is any.
- `{ around: 'center', movement: 'circular' | 'linear' }` turns around the map center.

Takes precedence over `options.dragRotate`.

```vue
<VMap :options="mapOptions" :drag-rotate="{ around: 'pointer' }"></VMap>
```

## Events

### @loaded

Emitted when the map has finished loading.

- **Payload:** `Map` - The MapLibre GL Map instance

```vue
<VMap @loaded="(map) => console.log('Map ready', map)"></VMap>
```

### Map Events

The VMap component forwards all MapLibre GL map events. You can listen to any map event using the `@` directive:

```vue
<VMap
  :options="mapOptions"
  @click="handleClick"
  @move="handleMove"
  @zoom="handleZoom"
  @load="handleLoad"
></VMap>
```

Common events include:

- `@load` - Map loaded
- `@click` - Map clicked
- `@dblclick` - Map double-clicked
- `@move` - Map moved
- `@movestart` - Map move started
- `@moveend` - Map move ended
- `@zoom` - Map zoomed
- `@zoomstart` - Zoom started
- `@zoomend` - Zoom ended

See [MapLibre GL Events](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#events) for the complete list.

## Slots

### default

The default slot is used to nest child components like markers, popups, layers, and controls.

```vue
<VMap :options="mapOptions">
  <VMarker :coordinates="[0, 0]" />
  <VLayerMaplibreGeojson :source="source" :layer="layer" />
  <VControlNavigation />
</VMap>
```

## Accessing the Map Instance

Child components can access the map instance using Vue's provide/inject API:

```vue
<script setup lang="ts">
  import { inject } from 'vue';
  import { MapKey } from '@geoql/v-maplibre/utils/symbols';
  import type { Map } from 'maplibre-gl';
  import type { Ref } from 'vue';

  const map = inject<Ref<Map | null>>(MapKey);

  // Use the map instance
  watch(map, (mapInstance) => {
    if (mapInstance) {
      console.log('Map instance available', mapInstance);
    }
  });
</script>
```

## TypeScript

The component is fully typed with TypeScript. Import types from `maplibre-gl`:

```typescript
import type { MapOptions, Map } from 'maplibre-gl';
```

## Examples

### Custom Style

```vue
<script setup lang="ts">
  import { VMap } from '@geoql/v-maplibre';

  const customStyle = {
    version: 8,
    sources: {
      osm: {
        type: 'raster',
        tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
        tileSize: 256,
        attribution: '© OpenStreetMap Contributors',
      },
    },
    layers: [
      {
        id: 'osm',
        type: 'raster',
        source: 'osm',
      },
    ],
  };

  const mapOptions = {
    style: customStyle,
    center: [0, 0],
    zoom: 2,
  };
</script>

<template>
  <VMap :options="mapOptions" style="height: 100vh"></VMap>
</template>
```

### With Bounds

```vue
<script setup lang="ts">
  import { VMap } from '@geoql/v-maplibre';
  import type { LngLatBoundsLike } from 'maplibre-gl';

  const bounds: LngLatBoundsLike = [
    [-74.047285, 40.679548], // Southwest
    [-73.907005, 40.882214], // Northeast
  ];

  const mapOptions = {
    style: 'https://demotiles.maplibre.org/style.json',
    bounds,
    fitBoundsOptions: {
      padding: 20,
    },
  };
</script>

<template>
  <VMap :options="mapOptions" style="height: 500px"></VMap>
</template>
```


## Sitemap

See the full [sitemap](https://v-maplibre.geoql.in/sitemap.md) for all pages.
