---
title: "Controls | JavaScript maps SDK"
description: "User interface elements that can be added to the map."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# Controls

The term “control” is commonly used for all sorts of buttons and information display that take place in one of the corner of the map area. The most well know are probably the `[+]` and `[-]` zoom buttons as well as the attribution information.

User interface elements that can be added to the map. The items in this section exist outside of the map’s `canvas` element.

## Easy to add controls ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

The easiest way to add the most used controls is through the [Map constructor options](https://docs.maptiler.com/sdk-js/api/map/#map-options).

To add a control in the map constructor, you must indicate the name of the control and one of these values: `true` to add the control or `false` to hide the control. You can also indicate in which position we are going to add the control: `top-left`, `top-right`, `bottom-left`, `bottom-right`.

### Example

```js
const map = new maptilersdk.Map({
  container: document.getElementById("my-container-div"),
  terrainControl: true,
  scaleControl: true,
  fullscreenControl: "top-left",
  geolocateControl: false
})
```

These are the controls that are directly accessible in the map constructor:

-   `navigationControl`: Shows the `[+]`, `[-]` zoom buttons and tilt/bearing/compass buttons. Showing on the `top-right` by default.
-   `geolocateControl`: Shows a arrow-shaped locate button. When clicked, it adds a marker and center the map. If clicked again, the marker disapears (unless the map was moved since first clicked). Showing on the `top-right` by default.
-   `terrainControl`: Shows a button to enable/disable the 3D terrain (does not tilt the map). Hidden by default, showing on the `top-right` if true.
-   `scaleControl`: Shows a distance scale. The unit (“metric”, “imperial” or “nautical”) can be set in the [config object](https://docs.maptiler.com/sdk-js/api/config/) config.unit (default: “metric”). Hidden by default, showing on the `bottom-right` if true.
-   `fullscreenControl`: Shows a button that toggles the map into fullscreen. Hidden by default, showing on the `top-right` if true.

## Custom controls ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

MapTiler SDK JS supports two flexible ways to add custom controls to your map interface, depending on the level of control and flexibility you need.

### Related examples

-   [How to add a custom control programmatically](https://docs.maptiler.com/sdk-js/examples/custom-controls-programmatic/)
-   [Add a custom control declarative way](https://docs.maptiler.com/sdk-js/examples/custom-controls-declarative/)

### Programmatic Controls

Programmatic controls allow developers to have more control and register custom control elements manually by calling `map.addControl()` and providing a control implementation. This method is ideal for applications that require dynamic logic, event-based behaviour, or a deeper integration with a framework like React.

Custom controls are instantiated using the [`MaptilerCustomControl`](#maptilercustomcontrol) class. The element that should be used can be provided either as the **element itself**, or as its **CSS selector**. Optionally, two callback functions can be provided:

-   `onClick` function that is called when the element is clicked, and
-   `onRender` function that is called every time the map renders a new state.

Both callbacks receive the active `Map` instance, the associated control element itself, and an event object associated with the original event (`PointerEvent` and `MapLibreEvent` respectively).

#### Example

```js
const panControl = new maptilersdk.MaptilerCustomControl(
  ".pan-control",
  (map) => map.panBy([10, 10]), // Move southeast when clicked
  (map, el) => el.classList.toggle( // Change class based on current hemisphere
    "northern-hemisphere", map.getCenter().lat > 0
  )
);
map.addControl(panControl);

const element = document.createElement("button");
element.className = "btn btn-primary pan-nw"
element.textContent = "Pan NW";

map.addControl(
  new maptilersdk.MaptilerCustomControl(
    element,
    (map) => map.panBy([-10, -10]) // Move northwest when clicked
  ),
  "top-left"
);
```

Example: [How to add a custom control programmatically](https://docs.maptiler.com/sdk-js/examples/custom-controls-programmatic/)

#### Behaviour Overview

-   Upon adding, the control element is removed from its original DOM position and inserted into the map UI.
-   The `onClick` callback binds an action to user interaction.
-   The `onRender` callback can be used for state-based updates, styling, or other custom logic.
-   The control is treated as a native part of the map UI but maintains its own DOM context.
-   Upon removing, the control element is moved back into its original DOM position (if any) to not interfere with DOM handling of frameworks like React.

### Declarative Controls

Declarative controls offer a simple way to add interactive UI elements to the map by using HTML attributes alone. Instead of instantiating controls through JavaScript, developers annotate DOM elements and allow the SDK to discover and wire them automatically.

Declarative controls are instantiated under the hood using the [`MaptilerExternalControl`](#maptilerexternalcontrol) class.

#### Example

[Add a custom control declarative way](https://docs.maptiler.com/sdk-js/examples/custom-controls-declarative/)

#### Enabling Detection

To activate declarative control detection:

-   Set the `customControls` option to `true` in the map initialization configuration, to enable detection globally.
-   Alternatively, `customControls` may be set to a **CSS selector string**, to scope the autodetection to:
    -   Elements matching the selector directly
    -   Or elements whose **ancestor** matches the selector

```js
const map = new maptilersdk.Map({
  container: "map",
  customControls: true, // or ".custom-ui"
});
```

#### Declaring a Control

To declare a control element, use the `data-maptiler-control` attribute:

```html
<button data-maptiler-control="zoom-in">+</button>
```

The attribute’s value must be one of the predefined keywords, or an empty value. The element is automatically registered as a control and moved into the map UI. Supported values:

| Value | Description |
| --- | --- |
| `zoom-in` | zooms the map in |
| `zoom-out` | zooms the map out |
| `toggle-projection` | toggles between Mercator and Globe projections |
| `toggle-terrain` | turns Terrain layer on and off |
| `reset-view` | resets bearing, pitch, and roll to 0 (heading north, no pitch, no roll) |
| `reset-bearing` | resets bearing to 0 (heading north) |
| `reset-pitch` | resets pitch to 0 (no pitch) |
| `reset-roll` | resets roll to 0 (no roll) |
| _empty value_ | registers the element as control but does not add any functionality automatically |

Warning

An Error is thrown when an unrecognized value is used.

#### Grouping Controls

For grouping related controls together, use the `data-maptiler-control-group` attribute. This approach is ideal for styling multiple buttons as a single floating UI block.

```html
<div data-maptiler-control-group>
  <button data-maptiler-control="zoom-in">+</button>
  <button data-maptiler-control="zoom-out">−</button>
</div>
```

-   The **group container** (`data-maptiler-control-group`) is registered as a control and moved into the map UI.
-   It does **not** receive any automatic functionality.
-   Functional behaviour is attached to valid descendant elements with `data-maptiler-control`.

#### Positioning Controls

To set a specific position for a control or group, use the `data-maptiler-position`. The allowed values are the same as in [addControl](https://docs.maptiler.com/sdk-js/api/map/#map#addcontrol) method.

```html
<button data-maptiler-control="reset-view" data-maptiler-position="top-left">↻</button>
<div data-maptiler-control-group data-maptiler-position="bottom-right">
  <button data-maptiler-control="zoom-in">+</button>
  <button data-maptiler-control="zoom-out">−</button>
</div>
```

#### State Styling via CSS

To support dynamic styling based on map state (without relying on JavaScript), custom CSS variables are set directly on the map container when declarative controls are enabled. Available CSS properties:

| Property | Description | Data type |
| --- | --- | --- |
| `--maptiler-center-lng` | Longitude of map center | unitless number |
| `--maptiler-center-lat` | Latitude of map center | unitless number |
| `--maptiler-zoom` | Current zoom level | unitless number |
| `--maptiler-bearing` | Current map bearing (rotation) | unitless number |
| `--maptiler-pitch` | Pitch angle | unitless number |
| `--maptiler-roll` | Roll angle | unitless number |
| `--maptiler-is-globe-projection` | `true` if globe view is enabled, `false` otherwise | string |
| `--maptiler-has-terrain` | `true` if terrain is active, `false` otherwise | string |

This enables responsive UI tweaks via pure CSS, for example:

```css
/* transform compass icon based on bearing and pitch */
.compass-icon {
  transform: rotateX(calc(var(--maptiler-pitch) * 1deg))
            rotateZ(calc(var(--maptiler-bearing) * -1deg));
}

/* change projection button icon when Globe projection is on */
@container style(--maptiler-is-globe-projection: true) {
  .projection-icon {
    content: "globe";
  }
}
```

## MaptilerNavigationControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

A `MaptilerNavigationControl` control contains zoom buttons and a compass.

Behavior changes:

-   When enabled, the pitch button is always present.
-   The pitch button now pitches the map if it was not (and as before, unpitches the map if pitched).
-   The compass icon on the pitch button has a capped “squeeziness” factor to prevent it from looking extra flat and wide when pitch is set higher than 60.

### Example

```js
const nav = new maptilersdk.MaptilerNavigationControl();
map.addControl(nav, 'top-left');
```

### API reference

[Class MaptilerNavigationControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerNavigationControl/)

### Related examples

-   [Display map navigation controls](https://docs.maptiler.com/sdk-js/examples/navigation/)
-   [Add a third party vector tile source](https://docs.maptiler.com/sdk-js/examples/third-party/)

## MaptilerGeolocateControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

A `MaptilerGeolocateControl` control provides a button that uses the browser’s geolocation API to locate the user on the map.

Not all browsers support geolocation, and some users may disable the feature. Geolocation support for modern browsers including Chrome requires sites to be served over HTTPS. If geolocation support is not available, the `MaptilerGeolocateControl` will show as disabled.

The zoom level applied will depend on the accuracy of the geolocation provided by the device.

The `MaptilerGeolocateControl` has two modes. If `trackUserLocation` is `false` (default) the control acts as a button, which when pressed will set the map’s camera to target the user location. If the user moves, the map won’t update. This is most suited for the desktop. If `trackUserLocation` is `true` the control acts as a toggle button that when active the user’s location is actively monitored for changes. In this mode the `MaptilerGeolocateControl` has three interaction states:

-   **active** - the map’s camera automatically updates as the user’s location changes, keeping the location dot in the center. Initial state and upon clicking the `MaptilerGeolocateControl` button.
-   **passive** - the user’s location dot automatically updates, but the map’s camera does not. Occurs upon the user initiating a map movement.
-   **disabled** - occurs if Geolocation is not available, disabled or denied.

These interaction states can’t be controlled programmatically, rather they are set based on user interactions.

Behavior changes:

-   Now with these defaults:
    -   `enableHighAccuracy`: true (uses browser location, probably GPS)
    -   `maximumAge`: 0 (not using any cached location)
    -   `Timeout`: 6000 (6 seconds)
    -   `trackUserLocation`: true
-   When geolocate is “active”, zooming/rotating/pitching the map no longer changes the status to “background” because it does not change the center of the map. The center of the map must move of at least 1 m (world) from the active locked position in order for the status to change to “background” (note: the “background” status means the location is still refreshed but if the user moves, the map will no longer continue to be centered on them).
-   The method to update the blue disc (location precision radius) has been improved as we zoom in. It is now less shaky and behave according to perspective when the camera is tilted.

Extends [Evented](https://docs.maptiler.com/sdk-js/api/events/#evented).

### Example

```js
map.addControl(new maptilersdk.MaptilerGeolocateControl({
  positionOptions: {
      enableHighAccuracy: true
  },
  trackUserLocation: true
}), 'top-left');
```

### API reference

[Class MaptilerGeolocateControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerGeolocateControl/)

### Related examples

-   [Locate the user](https://docs.maptiler.com/sdk-js/examples/geolocate-control/)

## MaptilerTerrainControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

The `MaptilerTerrainControl` shows a button to enable/disable the 3D terrain (does not tilt the map).

### Example

```js
const terrain3d = new maptilersdk.MaptilerTerrainControl();
map.addControl(terrain3d, 'top-left');
```

### API reference

[Class MaptilerTerrainControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerTerrainControl/)

### Related examples

-   [Display a 3D terrain map](https://docs.maptiler.com/sdk-js/examples/3d-map/)

## AttributionControl

An `AttributionControl` control presents the map’s attribution information. By default, the attribution control is expanded (regardless of map width).

### Example

```js
const map = new maptilersdk.Map({attributionControl: false})
.addControl(new maptilersdk.AttributionControl({
    compact: true
}));
```

### API reference

[Class AttributionControl](https://docs.maptiler.com/sdk-js/api-reference/classes/AttributionControl/)

## ScaleControl

A `ScaleControl` control displays the ratio of a distance on the map to the corresponding distance on the ground.

### Example

```js
const scale = new maptilersdk.ScaleControl({
  maxWidth: 80,
  unit: 'imperial'
});
map.addControl(scale);

scale.setUnit('metric');
```

### API reference

[Class ScaleControl](https://docs.maptiler.com/sdk-js/api-reference/classes/ScaleControl/)

## FullscreenControl

A `FullscreenControl` control contains a button for toggling the map in and out of fullscreen mode.

### Example

```js
map.addControl(new maptilersdk.FullscreenControl({container: document.querySelector('body')}));
```

### API reference

[Class FullscreenControl](https://docs.maptiler.com/sdk-js/api-reference/classes/FullscreenControl/)

### Related examples

-   [View a fullscreen map](https://docs.maptiler.com/sdk-js/examples/fullscreen/)

## MaptilerLogoControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

A `MaptilerLogoControl` replaces MaplibreLogoControl.

Can be used only with paid account.

### Example

```js
const logo = new maptilersdk.MaptilerLogoControl({
  logoURL: "https://api.maptiler.com/resources/logo.svg",
  linkURL: "https://www.maptiler.com"
});
map.addControl(logo, 'bottom-left');
```

### API reference

[Class MaptilerLogoControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerLogoControl/)

## MaptilerMinimapControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

A `MaptilerMinimapControl` control. Display a overview (minimap) in a user defined corner of the map.

### API reference

[Interface MinimapOptionsInput](https://docs.maptiler.com/sdk-js/api-reference/interfaces/MinimapOptionsInput/)

### Related examples

-   [How to display a minimap or overview map control to aid the map navigation](https://docs.maptiler.com/sdk-js/examples/control-minimap/)

## MaptilerProjectionControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

A `MaptilerProjectionControl` control contains a button for toggling the map projection between “mercator” and “globe”.

### Example

```js
map.addControl(new maptilersdk.MaptilerProjectionControl());
```

### API reference

[Class MaptilerProjectionControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerProjectionControl/)

### Related examples

-   [Projection control how to toggle the map between mercator and globe projection](https://docs.maptiler.com/sdk-js/examples/globe-control/)

## MaptilerExternalControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

The `MaptilerExternalControl` allows any existing element to automatically become a map control. Used for detected controls if `customControls` config is turned on.

### Example

```js
const zoomInControl = new maptilersdk.MaptilerExternalControl(
  ".zoom-in-control",
  "zoom-in"
);
```

### API reference

[Class MaptilerExternalControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerExternalControl/)

## MaptilerCustomControl ![MapTiler logo](https://docs.maptiler.com/favicon.ico)

The `MaptilerCustomControl` allows any existing element to become a map control.

### Example

```js
const panControl = new maptilersdk.MaptilerCustomControl(
  ".pan-control",
  (map) => map.panBy([10, 10]), // Move southeast when clicked
  (map, el) => el.classList.toggle( // Change class based on current hemisphere
    "northern-hemisphere", map.getCenter().lat > 0
  )
);
```

### API reference

[Class MaptilerCustomControl](https://docs.maptiler.com/sdk-js/api-reference/classes/MaptilerCustomControl/)

Note

Both callbacks receive the active `Map` instance, the associated control element itself, and an event object associated with the original event (`PointerEvent` and `MapLibreEvent` respectively).

### Related examples

-   [How to add a custom control programmatically](https://docs.maptiler.com/sdk-js/examples/custom-controls-programmatic/)

## IControl

Interface for interactive controls added to the map. This is a specification for implementers to model: it is not an exported method or class. This interface is the one to use to create custom controls.

Controls must implement `onAdd` and `onRemove`, and must own an element, which is often a `div` element. To use MapLibre GL JS’s default control styling, add the `maplibregl-ctrl` class to your control’s node.

### Example

```js
// Control implemented as ES6 class
class HelloWorldControl {
  onAdd(map) {
      this._map = map;
      this._container = document.createElement('div');
      this._container.className = 'maplibregl-ctrl';
      this._container.textContent = 'Hello, world';
      return this._container;
  }

  onRemove() {
      this._container.parentNode.removeChild(this._container);
      this._map = undefined;
  }
}

// Control implemented as ES5 prototypical class
function HelloWorldControl() { }

HelloWorldControl.prototype.onAdd = function(map) {
  this._map = map;
  this._container = document.createElement('div');
  this._container.className = 'maplibregl-ctrl';
  this._container.textContent = 'Hello, world';
  return this._container;
};

HelloWorldControl.prototype.onRemove = function () {
    this._container.parentNode.removeChild(this._container);
    this._map = undefined;
};
```

### API reference

[Interface IControl](https://docs.maptiler.com/sdk-js/api-reference/interfaces/IControl/)
