Skip to content

Controls

This library ships one control component, <GeolocateControls>. Every other MapTiler control — navigation, scale, fullscreen, attribution — is used as MapTiler's own class, constructed and handed to map.addControl().

There is no <NavigationControl> component, and there never was. If you find a snippet importing one from vue3-maptiler-gl, it is wrong.

The SDK adds two controls before you add any

<GeolocateControls> gives you a second geolocate button

Unlike MapLibre, the MapTiler SDK puts a navigation control and a geolocate control on every map it constructs: navigationControl and geolocateControl both default to true in MapOptions. So a bare <MapTiler> with no children already shows zoom in, zoom out, compass and locate — and adding <GeolocateControls> on top of that renders two locate buttons.

Turn the SDK's own off in :options when you are placing controls yourself:

ts
const mapOptions = {
  style: 'https://demotiles.maplibre.org/style.json',
  geolocateControl: false, // `<GeolocateControls>` provides this one
  navigationControl: false, // or leave it on and skip addControl below
};

terrainControl, scaleControl and fullscreenControl default to false and behave the way MapLibre users expect.

Geolocation

<GeolocateControls> wraps MapTiler's GeolocateControl and adds Vue events.

vue
<template>
  <MapTiler :options="mapOptions" style="height: 400px">
    <GeolocateControls
      position="top-right"
      :options="{ trackUserLocation: true }"
      @geolocate="onGeolocate"
      @error="onGeolocateError"
    />
  </MapTiler>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MapTiler, GeolocateControls } from 'vue3-maptiler-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [0, 0],
  zoom: 2,
});

function onGeolocate(position) {
  console.log(
    'Located at',
    position.coords.latitude,
    position.coords.longitude,
  );
}

function onGeolocateError(error) {
  console.error('Geolocation failed:', error.message);
}
</script>

Props and events

Both are tabulated in the reference: GeolocateControls. They are not repeated here — the tables on this page had already drifted from it once, advertising callback props named onGeolocate and onError that were renamed when it turned out Vue keys the geolocate and error emits' own listeners there.

Two things worth knowing before you read them: the event names are MapTiler's own, so @trackuserlocationstart rather than @trackingstart; and the callback props are onGeolocateSuccess, onGeolocateError, onTrackingStart, onTrackingEnd and onOutOfMaxBounds.

MapTiler's own controls

Import the class from the vue3-maptiler-gl/maptiler subpath. Since v6 the runtime lives there rather than at the package root, so importing one component does not pin the whole MapTiler runtime into your bundle.

vue
<template>
  <MapTiler :options="mapOptions" style="height: 400px" @load="onLoad" />
</template>

<script setup lang="ts">
import { ref, shallowRef, onBeforeUnmount } from 'vue';
import { MapTiler } from 'vue3-maptiler-gl';
import {
  NavigationControl,
  ScaleControl,
  FullscreenControl,
} from 'vue3-maptiler-gl/maptiler';
import type { Map } from 'vue3-maptiler-gl';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  center: [0, 0],
  zoom: 2,
});

const map = shallowRef<Map | null>(null);
const controls = shallowRef<any[]>([]);

function onLoad(instance: Map) {
  map.value = instance;

  controls.value = [
    [new NavigationControl({ showCompass: true }), 'top-right'],
    [new ScaleControl({ maxWidth: 100, unit: 'metric' }), 'bottom-left'],
    [new FullscreenControl(), 'top-left'],
  ];

  for (const [control, position] of controls.value) {
    instance.addControl(control, position);
  }
}

// The map is destroyed with the component, so this only matters if the controls
// outlive the map — but removing what you added keeps the intent explicit.
onBeforeUnmount(() => {
  for (const [control] of controls.value) map.value?.removeControl(control);
  controls.value = [];
});
</script>

Attribution is a special case: MapTiler adds one automatically. Pass attributionControl: false in the map options before adding your own, or you get two.

ts
import { AttributionControl } from 'vue3-maptiler-gl/maptiler';

const mapOptions = ref({
  style: 'https://demotiles.maplibre.org/style.json',
  attributionControl: false,
});

// then, on load:
instance.addControl(new AttributionControl({ compact: true }), 'bottom-right');

Adding a control from a composable

If you are outside a component that receives the map, useMapTiler() gives you the instance and a readiness flag. Both are refs — read them with .value.

ts
import { watchEffect, shallowRef } from 'vue';
import { useMapTiler } from 'vue3-maptiler-gl';
import { NavigationControl } from 'vue3-maptiler-gl/maptiler';

const { mapInstance, isMapReady } = useMapTiler();
const control = shallowRef<NavigationControl | null>(null);

watchEffect((onCleanup) => {
  if (!isMapReady.value || !mapInstance.value) return;

  const map = mapInstance.value;
  control.value = new NavigationControl();
  map.addControl(control.value, 'top-right');

  onCleanup(() => {
    if (control.value) map.removeControl(control.value);
    control.value = null;
  });
});

useGeolocateControl is the composable equivalent of <GeolocateControls> and handles this bookkeeping for you:

ts
import { useGeolocateControl } from 'vue3-maptiler-gl';

const { geolocateControl, isControlAdded, removeControl } = useGeolocateControl(
  {
    map: mapInstance,
    position: 'top-right',
    options: { trackUserLocation: true },
  },
);

A custom control

MapTiler's control contract is an object with onAdd and onRemove. Anything satisfying it can be added the same way.

ts
import type { IControl, Map } from 'vue3-maptiler-gl';

class ResetViewControl implements IControl {
  private container!: HTMLDivElement;
  private map!: Map;

  onAdd(map: Map) {
    this.map = map;
    this.container = document.createElement('div');
    this.container.className = 'maptilersdk-ctrl maptilersdk-ctrl-group';

    const button = document.createElement('button');
    button.type = 'button';
    button.setAttribute('aria-label', 'Reset view');
    button.textContent = '⌂';
    button.addEventListener('click', () => {
      this.map.flyTo({ center: [0, 0], zoom: 2 });
    });

    this.container.appendChild(button);
    return this.container;
  }

  onRemove() {
    this.container.remove();
  }
}

// instance.addControl(new ResetViewControl(), 'top-left');

Reuse MapTiler's maptilersdk-ctrl maptilersdk-ctrl-group classes and your control inherits the built-in styling and spacing.