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.
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¢er=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¢er=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.
Installing it alongside Maps JS
npm install @maptoolkit/maplibre-style-controlimport { 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¢er=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
- Style Control plugin for the full options, methods and events, and for using it with plain MapLibre GL JS.
- Vector Tiles for what is in each of the seven styles.
- Static Maps API for generating your own thumbnails.