---
title: "Add a custom control declarative way | JavaScript maps SDK"
description: "Add interactive custom controls to maps using declarative HTML."
---

> 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.

# Add a custom control declarative way

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.

To activate declarative control detection set the `customControls` option to `true` in the map initialization configuration. Alternatively, customControls may be set to a **CSS selector string**, restricting autodetection to:

-   Elements matching the selector directly
-   Or elements whose **ancestor** matches the selector

### NPM module setup

```bash
npm install --save @maptiler/sdk bootstrap
```

#### main.js

```js
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import 'bootstrap/dist/css/bootstrap.min.css';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const map = new Map({
  container: 'map', // container's id or the HTML element to render the map
  style: MapStyle.STREETS,
  customControls: true,
  navigationControl: false,
  geolocateControl: false,
  terrainExaggeration: 4,
});

//custom control (center map)
document.querySelector(".home-button")?.addEventListener("click", () => map.easeTo({ center: [15, 50], zoom: 7 }));
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Add a custom control declarative way | NPM example</title>
  <link rel="stylesheet" href="./style.css">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:FILL@0..1">
</head>
<body>
  <div id="map"></div>
  <div data-maptiler-control-group class="btn-group-vertical m-2">
    <button data-maptiler-control="zoom-in" class="btn btn-light">
      <span class="material-symbols-outlined">zoom_in</span>
    </button>
    <button data-maptiler-control="zoom-out" class="btn btn-light">
      <span class="material-symbols-outlined">zoom_out</span>
    </button>
    <button data-maptiler-control="reset-view" class="btn btn-light">
      <span class="material-symbols-outlined">navigation</span>
    </button>
  </div>
  <button data-maptiler-control="toggle-projection" data-maptiler-position="top-left" class="btn btn-primary m-2">
    <span class="material-symbols-outlined"></span>
  </button>
  <button data-maptiler-control="toggle-terrain" data-maptiler-position="top-left" class="btn btn-success m-2">
    <span class="material-symbols-outlined">terrain</span>
  </button>
  <button data-maptiler-control data-maptiler-position="bottom-left" class="btn btn-danger m-2 float-end home-button">
    <span class="material-symbols-outlined">home</span>
  </button>
  <script type="module" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
.maplibregl-control-container .btn {
  pointer-events: auto;
  display: block;
}

[data-maptiler-control="reset-view"] span {
  transform: rotateX(calc(var(--maptiler-pitch) * 1deg))
    rotateZ(calc(var(--maptiler-bearing) * -1deg));
}
[data-maptiler-control="toggle-projection"] span::after {
  content: "globe";
}
@container style(--maptiler-is-globe-projection: true) {
  [data-maptiler-control="toggle-projection"] span::after {
    content: "map";
  }
}
[data-maptiler-control="toggle-terrain"] span, .home-button span {
  transition: font-variation-settings 1s ease-in-out;
  font-variation-settings: 'FILL' 0;
}
@container style(--maptiler-has-terrain: true) {
  [data-maptiler-control="toggle-terrain"] span {
    font-variation-settings: 'FILL' 1;
  }
}
@container style(--maptiler-center-lng: 15) and style(--maptiler-center-lat: 50) {
  .home-button span {
    font-variation-settings: 'FILL' 1;
  }
}
```

### Basic JavaScript setup

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>MapTiler custom controls</title>
  <script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
  <link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:FILL@0..1">
  <style>
    body { margin: 0; padding: 0; }
    #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    .maplibregl-control-container .btn {
      pointer-events: auto;
      display: block;
    }

    [data-maptiler-control="reset-view"] span {
      transform: rotateX(calc(var(--maptiler-pitch) * 1deg))
        rotateZ(calc(var(--maptiler-bearing) * -1deg));
    }
    [data-maptiler-control="toggle-projection"] span::after {
      content: "globe";
    }
    @container style(--maptiler-is-globe-projection: true) {
      [data-maptiler-control="toggle-projection"] span::after {
        content: "map";
      }
    }
    [data-maptiler-control="toggle-terrain"] span, .home-button span {
      transition: font-variation-settings 1s ease-in-out;
      font-variation-settings: 'FILL' 0;
    }
    @container style(--maptiler-has-terrain: true) {
      [data-maptiler-control="toggle-terrain"] span {
        font-variation-settings: 'FILL' 1;
      }
    }
    @container style(--maptiler-center-lng: 15) and style(--maptiler-center-lat: 50) {
      .home-button span {
        font-variation-settings: 'FILL' 1;
      }
    }
  </style>
</head>
<body>
  <div id="map"></div>
  <div data-maptiler-control-group class="btn-group-vertical m-2">
    <button data-maptiler-control="zoom-in" class="btn btn-light">
      <span class="material-symbols-outlined">zoom_in</span>
    </button>
    <button data-maptiler-control="zoom-out" class="btn btn-light">
      <span class="material-symbols-outlined">zoom_out</span>
    </button>
    <button data-maptiler-control="reset-view" class="btn btn-light">
      <span class="material-symbols-outlined">navigation</span>
    </button>
  </div>
  <button data-maptiler-control="toggle-projection" data-maptiler-position="top-left" class="btn btn-primary m-2">
    <span class="material-symbols-outlined"></span>
  </button>
  <button data-maptiler-control="toggle-terrain" data-maptiler-position="top-left" class="btn btn-success m-2">
    <span class="material-symbols-outlined">terrain</span>
  </button>
  <button data-maptiler-control data-maptiler-position="bottom-left" class="btn btn-danger m-2 float-end home-button">
    <span class="material-symbols-outlined">home</span>
  </button>
  <script>
    maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
    const map = new maptilersdk.Map({
      container: 'map', // container's id or the HTML element to render the map
      style: maptilersdk.MapStyle.STREETS,
      customControls: true,
      navigationControl: false,
      geolocateControl: false,
      terrainExaggeration: 4,
    });

    //custom control (center map)
    document.querySelector(".home-button")?.addEventListener("click", () => map.easeTo({ center: [15, 50], zoom: 7 }));
  </script>
</body>
</html>
```

## Learn more

Check out the [MaptilerExternalControl API reference](https://docs.maptiler.com/sdk-js/api/controls/#maptilerexternalcontrol) to learn all the options to add custom controls to your map interface.

## Related examples

### [Custom control programmatically](https://docs.maptiler.com/sdk-js/examples/custom-controls-programmatic/)

Examples

Programmatically add custom controls for dynamic map logic integration.

### [Add custom zoom control](https://docs.maptiler.com/sdk-js/examples/custom-control/)

Examples

Add custom zoom level control to your map interface.

### [Hide map navigation controls](https://docs.maptiler.com/sdk-js/examples/navigation/)

Tutorials

Hide the map navigation and zoom controls.

### [Scale control](https://docs.maptiler.com/sdk-js/examples/control-scale/)

Tutorials

Learn to add and display map scale control.
