Add a Style Switcher to MapLibre GL JS
@maptoolkit/maplibre-style-control
is a MapLibre GL JS control that gives your users a style switcher: a panel of thumbnails,
one per basemap, that swaps the map style when clicked. It is open source under BSD 3-Clause
and maintained at
github.com/maptoolkit/maplibre-style-control.
The demo below uses the seven Maptoolkit styles, each loaded from a keyed style URL.
Install
npm install @maptoolkit/maplibre-style-control maplibre-glimport maplibregl from "maplibre-gl";
import { StyleControl } from "@maptoolkit/maplibre-style-control";
import "@maptoolkit/maplibre-style-control/style.css";
const map = new maplibregl.Map({ container: "map", style, center, zoom });
map.addControl(new StyleControl());The stylesheet is a separate import and the control renders unstyled without it.
It also ships a UMD build, so it works from a script tag with no bundler. The global is
MaplibreStyleControl:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.0.2/dist/maplibre-style-control.css" />
<script src="https://cdn.jsdelivr.net/npm/@maptoolkit/maplibre-style-control@1.0.2/dist/maplibre-style-control.js"></script>
<script>
map.addControl(new MaplibreStyleControl.StyleControl());
</script>Requires maplibre-gl 3.0.0 or later as a peer dependency.
Point it at your own styles
This is the part to get right. The plugin’s built-in styles are the seven community styles
from styles.maptoolkit.org, which take no API key and are served under the Maptoolkit
Community License. Adding the control with its defaults therefore replaces whatever style your
map was built with, on the first render, and moves the map onto the community service.
If you are a Maptoolkit customer, pass your own styles array so the map stays on the styles
your key pays for:
const API_KEY = "YOUR_API_KEY";
const style = (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: style(n),
image: `https://staticmap.maptoolkit.net?maptype=maptoolkit-maptoolkit.${n}&size=120x120¢er=47.310897,12.805988&zoom=12.1`,
}));
map.addControl(new StyleControl({ styles, active: "Summer" }), "top-left");The thumbnails above come from the Static Maps API, so they show the real cartography at a location you choose rather than a generic sample. Any image URL works.
Position it away from the logo
The control’s default position is bottom-left, which is also where the Maptoolkit logo goes.
Left at the default the two overlap, and attribution that is covered does not count as
displayed.
An open panel also overlaps anything else in its own corner, including the zoom control, and
the panel has no z-index of its own, so whichever control was added last paints on top. Give
the switcher a corner to itself:
map.addControl(new StyleControl(), "top-left");The panel opens from whichever corner you choose, so all four work. With the logo
bottom-left and the zoom control top-right, top-left is the corner that stays free.
Options
| Option | Type | Default | Description |
|---|---|---|---|
styles | StyleDefSpecification[] | the seven community styles | Styles offered in the panel. |
active | string | "Summer" | id of the style selected on load. |
StyleDefSpecification:
| Field | Type | Description |
|---|---|---|
id | string | Unique id. Also the label, and the key used for translation. |
value | string | StyleSpecification | A style URL, or an inline style object. |
image | string? | Thumbnail shown in the panel. |
To add to the built-in list rather than replace it, spread defaultStyleControlOptions:
import { StyleControl, defaultStyleControlOptions } from "@maptoolkit/maplibre-style-control";
new StyleControl({
styles: [...defaultStyleControlOptions.styles, { id: "Custom", value: "https://example.com/style.json" }],
});Note that this keeps the community styles in the list, so a customer map would offer both keyed and keyless styles side by side.
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. Any other id, including a lower-case spelling of a built-in one, throws
Missing UI string 'StyleControl.Style.<id>' and the control does not render at all. The map
is left with no switcher and an error in the console.
Register the label in the map’s locale and any id works:
const map = new maplibregl.Map({
container: "map",
style: "...",
locale: { "StyleControl.Style.Brand": "Brand" },
});
map.addControl(new StyleControl({ styles: [{ id: "Brand", value: "..." }] }), "top-left");The seven ids that need no entry are Summer, Winter, Light, Dark, Cycling, Hiking
and Street, spelled exactly like that.
Methods
| Method | Description |
|---|---|
setStyle(styleId) | Switches to the style with that id, as if it had been clicked. Fires style.set. |
open() | Opens the panel. |
close() | Closes the panel. |
setStyle is attached when the control is added to a map, so it is undefined until after
map.addControl(control).
Events
The control extends MapLibre’s Evented, so it is subscribed to like the map itself.
| Event | Payload | Fired when |
|---|---|---|
style.set | { style: StyleDefSpecification } | The active style changes. |
const control = new StyleControl();
map.addControl(control);
control.on("style.set", (e) => console.log(e.style.id));It fires on the initial render as well as on user clicks, so a handler that persists the choice will see one event before the user has touched anything.
Restoring your layers after a switch
Changing the style throws away every source and layer on the map, including ones you added. Re-add yours when 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" });
}
});
});Theming
Appearance is driven by CSS custom properties on .maplibre-style-control, so it can be
themed from your own stylesheet without touching the plugin:
.maplibre-style-control {
--style-control-radius: 4px;
--style-control-color-primary: #0074d9;
}The full list of --style-control-* variables is in src/style.css in the repository.
Using it with Maps JS
This plugin works on a Maps JS map too, since Maps JS extends MapLibre GL JS and
the plugin is a plain IControl.
Add a Style Switcher in Maptoolkit Maps JS is the
worked example, including the keyed-style list and the alias below.
Note that Maps JS exports a class of its own called StyleControl. It is a different control
that happens to share the name, and it is not recommended for new work, so with both loaded
keep them apart: MaplibreStyleControl.StyleControl is this plugin,
maptoolkit.StyleControl is the other one.
One thing differs:
- From a script tag it needs a
maplibreglglobal. The UMD build expectswindow.maplibregland Maps JS provideswindow.maptoolkit, so without an alias the control fails to construct withCannot read properties of undefined (reading 'Evented'). Setwindow.maplibregl = window.maptoolkit;after the Maps JS script and before the plugin.
Next steps
- MapLibre GL JS for the rest of the setup, including the API key and the attribution requirement.
- Vector Tiles for the style URLs and what is in each style.
- Static Maps API for generating the thumbnails.