Add a Style Switcher in MapLibre GL JS
A style switcher lets the reader pick the basemap: a panel of thumbnails, one per style, that changes the map style when clicked. This example uses the Style Control plugin on MapLibre GL JS 5.6.1, loaded from a CDN without a bundler.
import { LogoControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.js";
import { StyleControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.1.2/dist/maplibre-style-control.js";
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 maplibregl.Map({
container: "map",
style: styleUrl("summer"),
center: [11.39085, 47.27574],
zoom: 12,
attributionControl: { compact: false },
});
const control = new StyleControl({ styles, active: "Summer" });
map.addControl(control, "top-left");
map.addControl(new maplibregl.NavigationControl(), "top-right");
control.on("style.set", (e) => console.log("style.set", e.style.id));
map.addControl(new LogoControl({ variant: "com" }));<!DOCTYPE html>
<html lang="en">
<head>
<title>Add a Style Switcher - MapLibre GL JS</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@5.6.1/dist/maplibre-gl.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.1.2/dist/maplibre-style-control.css" />
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5.6.1/dist/maplibre-gl.js"></script>
<!-- The plugin is an ES module that imports from "maplibre-gl". MapLibre 5 ships no module
build, so this import map points "maplibre-gl" to a shim that re-exports the global. -->
<script type="importmap">
{
"imports": {
"maplibre-gl": "data:text/javascript,const m=window.maplibregl;export default m;export const{Evented,LngLatBounds,Map,Marker}=m;"
}
}
</script>
<style>
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
#map { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script type="module">
import { LogoControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-logo-control@1.1.2/dist/maplibre-logo-control.js";
import { StyleControl } from "https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.1.2/dist/maplibre-style-control.js";
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 maplibregl.Map({
container: "map",
style: styleUrl("summer"),
center: [11.39085, 47.27574],
zoom: 12,
attributionControl: { compact: false },
});
const control = new StyleControl({ styles, active: "Summer" });
map.addControl(control, "top-left");
map.addControl(new maplibregl.NavigationControl(), "top-right");
control.on("style.set", (e) => console.log("style.set", e.style.id));
map.addControl(new LogoControl({ variant: "com" }));
</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.
Load the plugin without a bundler
The plugin is an ES module that imports from maplibre-gl. MapLibre GL JS 6 ships an ES module
build, so on version 6 an import map that points maplibre-gl to that build is enough. See the
plugin page.
MapLibre GL JS 5 only ships a script build that creates the global maplibregl. To use the
plugin with it, the import map points maplibre-gl to a small shim module that exports that
global:
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@5.6.1/dist/maplibre-gl.js"></script>
<script type="importmap">
{
"imports": {
"maplibre-gl": "data:text/javascript,const m=window.maplibregl;export default m;export const{Evented,LngLatBounds,Map,Marker}=m;"
}
}
</script>Keep three things in mind:
- The import map must come before the first
<script type="module">on the page. - The shim lists every name that a Maptoolkit plugin imports from
maplibre-gl. The same shim works for the Style, Logo, Isochrone and Legend controls. - Load the plugin from its
dist/*.jsfile. jsDelivr’s+esmURLs resolvemaplibre-glon their own and load a second copy of MapLibre GL JS 6.
With npm and a bundler you do not need the shim. The plugin declares maplibre-gl 6 as a peer
dependency, so on MapLibre GL JS 5 install it with --legacy-peer-deps:
npm install maplibre-gl@5.6.1 @maptoolkit/maplibre-style-control --legacy-peer-depsPoint it at your keyed styles
The plugin ships with the seven community styles from styles.maptoolkit.org, which take no
API key. If you add the control with its defaults, it 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 has 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 map at a location you pick. Any image URL works.
The control shows the id as the label. For the seven built-in ids (Summer, Winter, Light,
Dark, Cycling, Hiking and Street) it has its own labels. To show a different label for
any id, add it to the map’s locale:
const map = new maplibregl.Map({
container: "map",
attributionControl: { compact: false },
locale: { "StyleControl.Style.Brand": "Our brand style" },
});Give it a corner of its own
The control defaults to bottom-left, and an open panel covers anything in the same corner. It
has no z-index, so a control added after it is drawn 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 for the switcher and
top-right for navigation.
map.addControl(control, "top-left");
map.addControl(new maplibregl.NavigationControl(), "top-right");If your map shows the Maptoolkit logo in the bottom-left corner, keep the switcher away from that corner too. Attribution that is covered does not count as displayed.
React to a change
The control extends MapLibre’s Evented, so you subscribe to it the same way as to the map:
control.on("style.set", (e) => console.log(e.style.id));It fires on the first render as well as on clicks, so a handler that saves the reader’s choice gets one event before anyone has used the control.
setStyle(id) switches the style from code, and open() and close() open and close the panel.
setStyle is only available after addControl.
Add your layers again after a switch
Changing the style removes every source and layer on the map, including the ones you added. Add them again 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 all options, methods and events.
- Our MapLibre Plugins for the other Maptoolkit controls.
- Vector Tiles for what is in each of the seven styles.