Skip to content
Add a Style Switcher

Add a Style Switcher in Maptoolkit Maps JS

A style switcher lets the reader pick the basemap: a panel of thumbnails, one per style, that swaps the map style when clicked.

Use @maptoolkit/maplibre-style-control for this. It is a MapLibre GL JS control, BSD 3-Clause, and because Maps JS extends MapLibre GL JS it works on a Maps JS map with one line of setup.

Open the full-screen demo

window.maplibregl = window.maptoolkit;

const API_KEY = 'YOUR_API_KEY';
    const styleUrl = (name) =>
        `https://styles.maptoolkit.net/maptoolkit/maptoolkit.${name}.json?api_key=${API_KEY}`;
    const thumb = (name) =>
        `https://staticmap.maptoolkit.net?maptype=maptoolkit-maptoolkit.${name}&size=120x120&center=47.27574,11.39085&zoom=12`;

    const styles = ['summer', 'winter', 'hiking', 'cycling', 'street', 'light', 'dark'].map((n) => ({
        id: n[0].toUpperCase() + n.slice(1),
        value: styleUrl(n),
        image: thumb(n)
    }));

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: styleUrl('summer'),
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    const control = new MaplibreStyleControl.StyleControl({ styles, active: 'Summer' });
    map.addControl(control, 'top-left');
    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    control.on('style.set', (e) => console.log('style.set', e.style.id));
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Add a Style Switcher - Maptoolkit Maps JS</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
    <!-- The plugin's UMD build looks for a global called maplibregl.
         Maps JS provides maptoolkit, so alias it before the plugin loads. -->
    <script>window.maplibregl = window.maptoolkit;</script>
    <script src="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.0.2/dist/maplibre-style-control.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.0.2/dist/maplibre-style-control.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
    </style>
</head>
<body>
<div id="map"></div>
<script>
    const API_KEY = 'YOUR_API_KEY';
    const styleUrl = (name) =>
        `https://styles.maptoolkit.net/maptoolkit/maptoolkit.${name}.json?api_key=${API_KEY}`;
    const thumb = (name) =>
        `https://staticmap.maptoolkit.net?maptype=maptoolkit-maptoolkit.${name}&size=120x120&center=47.27574,11.39085&zoom=12`;

    const styles = ['summer', 'winter', 'hiking', 'cycling', 'street', 'light', 'dark'].map((n) => ({
        id: n[0].toUpperCase() + n.slice(1),
        value: styleUrl(n),
        image: thumb(n)
    }));

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: styleUrl('summer'),
        center: [11.39085, 47.27574],
        zoom: 12,
        attributionControl: { compact: false }
    });

    const control = new MaplibreStyleControl.StyleControl({ styles, active: 'Summer' });
    map.addControl(control, 'top-left');
    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    control.on('style.set', (e) => console.log('style.set', e.style.id));
</script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map centered on [11.39085, 47.27574] at zoom 12 with a style switcher control in the top-left corner that offers the seven Maptoolkit styles.

Installing it alongside Maps JS

npm install @maptoolkit/maplibre-style-control
import { StyleControl } from "@maptoolkit/maplibre-style-control";
import "@maptoolkit/maplibre-style-control/style.css";

The stylesheet is a separate import. Without it the control renders unstyled.

From a script tag it needs a maplibregl global. The UMD build treats maplibre-gl as an external and looks for window.maplibregl. Maps JS publishes window.maptoolkit instead, so alias it after the Maps JS script and before the plugin’s:

<script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
<script>window.maplibregl = window.maptoolkit;</script>
<script src="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.0.2/dist/maplibre-style-control.js"></script>

Without the alias the control fails to construct and the console reads Cannot read properties of undefined (reading 'Evented'), which points at the plugin rather than at the missing global.

Maps JS also exports a class of its own called StyleControl, so with both loaded the bare name is ambiguous. MaplibreStyleControl.StyleControl is this plugin.

Point it at your keyed styles

The plugin ships with the seven community styles from styles.maptoolkit.org, which take no API key. Adding the control with its defaults therefore replaces the style your map was built with, on the first render, and moves the map onto the community service.

Pass your own styles array so the map stays on the styles your key pays for:

const styleUrl = (name) =>
  `https://styles.maptoolkit.net/maptoolkit/maptoolkit.${name}.json?api_key=${API_KEY}`;

const styles = ['summer', 'winter', 'hiking', 'cycling', 'street', 'light', 'dark'].map((n) => ({
  id: n[0].toUpperCase() + n.slice(1),
  value: styleUrl(n),
  image: `https://staticmap.maptoolkit.net?maptype=maptoolkit-maptoolkit.${n}&size=120x120&center=47.27574,11.39085&zoom=12`
}));

Each entry is an id, a value that is a style URL or an inline style object, and an image for the thumbnail. The thumbnails above come from the Static Maps API, so they show the real cartography at a location you pick. Any image URL works.

A custom id needs a matching label. The id doubles as the translation key, and the control only registers labels for its seven built-in ids: Summer, Winter, Light, Dark, Cycling, Hiking and Street, spelled exactly like that. Any other id, including a lower-case spelling of one of those, throws Missing UI string 'StyleControl.Style.<id>' and the control does not render at all. Register the label in the map’s locale and any id works:

const map = new maptoolkit.Map({
  container: 'map',
  apiKey: API_KEY,
  locale: { 'StyleControl.Style.Brand': 'Brand' },
});

The example above sidesteps this by capitalising the seven names, which happens to match the built-in ids.

Give it a corner of its own

The control defaults to bottom-left, and an open panel overlaps anything sharing its corner. It carries no z-index, so a control added after it paints on top: put it and the zoom buttons in one corner and the zoom buttons sit over the open panel. The demo uses top-left with navigation at top-right.

map.addControl(control, 'top-left');
map.addControl(new maptoolkit.NavigationControl(), 'top-right');

If your map shows a Maptoolkit logo bottom-left, keep the switcher away from that corner too. Attribution that is covered does not count as displayed.

Reacting to a change

The control extends MapLibre’s Evented, so subscribe to it the way you would to the map:

control.on('style.set', (e) => console.log(e.style.id));

It fires on the initial render as well as on clicks, so a handler that saves the reader’s choice sees one event before anyone has touched the control.

setStyle(id) switches programmatically, and open() and close() drive the panel. setStyle is attached when the control is added to a map, so it is undefined until after addControl.

Re-add your layers after a switch

Changing the style throws away every source and layer on the map, including the ones you added. Put them back once the new style has loaded:

control.on('style.set', () => {
  map.once('styledata', () => {
    if (!map.getSource('route')) {
      map.addSource('route', { type: 'geojson', data: routeGeoJSON });
      map.addLayer({ id: 'route', type: 'line', source: 'route' });
    }
  });
});

Next steps