map-gl-style-switcher
v0.10.0
Published
A customizable style switcher control for Mapbox GL JS and MapLibre GL JS
Maintainers
Readme
Map GL Style Switcher
A TypeScript control for switching Mapbox GL / MapLibre GL map styles. Easily add a floating style switcher to your map app, with support for multiple styles, images, dark/light themes, and before/after change callbacks.
Available as:
StyleSwitcherControl- Direct IControl implementation for Mapbox/MapLibre GLMapGLStyleSwitcher- React component for react-map-gl integrationuseStyleSwitcher- React hook for custom map instances
🌐 Live Demo
Animated Demo

Available Styles

Features
- IControl implementation for Mapbox GL / MapLibre GL
- React component for react-map-gl integration
- React hook for custom map instances
- Floating style switcher in any corner (via map.addControl position)
- Support for multiple map styles with thumbnails
- Expand/collapse on hover with smooth animations
- Dark/light/auto theme support —
theme: 'auto'reacts to OS preference changes in real time - RTL text support for Arabic/Hebrew interfaces
- Configurable display options (show/hide labels and images)
- Callbacks for before/after style change
updateOptions()for imperative option updates after mount- Fully customizable CSS classes
- TypeScript support
- Accessibility features (ARIA labels, keyboard navigation)
- ESM-only package (tree-shakeable, no CommonJS bundle)
- Comprehensive test coverage
Install
# Using npm (recommended)
npm install map-gl-style-switcher
# Using yarn
yarn add map-gl-style-switcher
# Using pnpm
pnpm add map-gl-style-switcherNote: This package ships ES modules only. CommonJS
require()is not supported. All modern bundlers (Vite, webpack 5, Rollup, esbuild) handle ESM natively.
Usage
Vanilla JS / Direct MapLibre or Mapbox GL
import { StyleSwitcherControl, type StyleItem } from 'map-gl-style-switcher';
import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
const styles: StyleItem[] = [
{
id: 'voyager',
name: 'Voyager',
image:
'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/voyager.png',
styleUrl: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
description: 'Voyager style from Carto',
},
{
id: 'positron',
name: 'Positron',
image:
'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/positron.png',
styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
description: 'Positron style from Carto',
},
];
const control = new StyleSwitcherControl({
styles,
activeStyleId: 'voyager',
theme: 'auto',
onAfterStyleChange: (_from, to) => {
map.setStyle(to.styleUrl);
},
});
map.addControl(control, 'bottom-left');React — useStyleSwitcher hook (custom map instance)
Use this when you manage the map instance yourself (e.g., inside useEffect):
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import { useStyleSwitcher, type StyleItem } from 'map-gl-style-switcher/react';
import 'maplibre-gl/dist/maplibre-gl.css';
import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
const styles: StyleItem[] = [
/* ... */
];
function MapComponent() {
const mapContainer = useRef<HTMLDivElement>(null);
const mapRef = useRef<maplibregl.Map | null>(null);
useEffect(() => {
if (!mapContainer.current) return;
mapRef.current = new maplibregl.Map({
container: mapContainer.current,
style: styles[0].styleUrl,
center: [0, 0],
zoom: 2,
});
return () => mapRef.current?.remove();
}, []);
useStyleSwitcher(mapRef.current, {
styles,
theme: 'auto',
position: 'bottom-left',
onAfterStyleChange: (_from, to) => {
mapRef.current?.setStyle(to.styleUrl);
},
});
return <div ref={mapContainer} style={{ width: '100%', height: '500px' }} />;
}Tip: The hook recreates the control whenever options change (deep comparison via JSON serialisation). Pass stable object references or memoize options if performance is critical.
React — MapGLStyleSwitcher component (react-map-gl)
Use this when your map is rendered by react-map-gl:
import { useState } from 'react';
import { Map } from 'react-map-gl/maplibre';
import { MapGLStyleSwitcher, type StyleItem } from 'map-gl-style-switcher/react-map-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import 'map-gl-style-switcher/dist/map-gl-style-switcher.css';
const styles: StyleItem[] = [
/* ... */
];
function MyMap() {
const [mapStyle, setMapStyle] = useState(styles[0].styleUrl);
const [activeStyleId, setActiveStyleId] = useState(styles[0].id);
const handleStyleChange = (styleUrl: string) => {
setMapStyle(styleUrl);
const style = styles.find(s => s.styleUrl === styleUrl);
if (style) setActiveStyleId(style.id);
};
return (
<Map mapStyle={mapStyle} initialViewState={{ longitude: 0, latitude: 0, zoom: 2 }}>
<MapGLStyleSwitcher
styles={styles}
activeStyleId={activeStyleId}
theme="auto"
position="bottom-left"
onStyleChange={handleStyleChange}
/>
</Map>
);
}MapGLStyleSwitcher propagates all non-callback prop changes to the underlying control after mount — changing activeStyleId, theme, showLabels, styles, etc. is fully reactive. Callbacks (onBeforeStyleChange, onAfterStyleChange, onStyleChange) are always up-to-date via refs and do not cause the control to be recreated.
Examples
MapLibre GL Example
React Map GL Example
View the react-map-gl example →
Configuration Options
interface StyleSwitcherControlOptions {
styles: StyleItem[]; // Array of map styles (required)
activeStyleId?: string; // Active style ID (default: first style)
onBeforeStyleChange?: (from: StyleItem, to: StyleItem) => void; // Called before style changes
onAfterStyleChange?: (from: StyleItem, to: StyleItem) => void; // Called after style changes
showLabels?: boolean; // Show style names (default: true)
showImages?: boolean; // Show thumbnails (default: true)
animationDuration?: number; // Expand animation in ms (default: 200)
maxHeight?: number; // Max list height in px (default: 300)
theme?: 'light' | 'dark' | 'auto'; // UI theme (default: 'light')
classNames?: Partial<StyleSwitcherClassNames>; // Custom CSS class overrides
rtl?: boolean; // RTL layout (default: false)
}
interface StyleItem {
id: string; // Unique identifier
name: string; // Display name
image: string; // Thumbnail URL or data URI
styleUrl: string; // MapLibre/Mapbox style URL
description?: string; // Optional tooltip text
}
interface StyleSwitcherClassNames {
container: string; // Main container class
list: string; // Expanded list container class
item: string; // Individual style item class
itemSelected: string; // Selected item class
itemHideLabel: string; // No-label utility class
dark: string; // Dark theme class
light: string; // Light theme class
}Option details
activeStyleId: Controls both the initially selected style and what is shown in the collapsed state. When usingMapGLStyleSwitcher, updating this prop reactively switches the highlighted style.showLabels&showImages: At least one must betrue(throws otherwise).theme:'light': Light colour scheme (default)'dark': Dark colour scheme'auto': Follows the OS preference and updates automatically when the user switches between light and dark mode
rtl: Enables right-to-left layout for Arabic/Hebrew interfaces.
Imperative updates — updateOptions()
After adding the control to a map you can update its options without recreating it:
const control = new StyleSwitcherControl({ styles, activeStyleId: 'voyager' });
map.addControl(control, 'bottom-left');
// Later — switch active style programmatically
control.updateOptions({ activeStyleId: 'positron' });
// Or change multiple options at once
control.updateOptions({ theme: 'dark', showLabels: false });Only the keys you pass are updated; all others remain unchanged. If the control has already been added to the map, the UI re-renders immediately.
Customizing CSS Classes
Override all CSS classes used by the control via the classNames option:
const control = new StyleSwitcherControl({
styles,
classNames: {
container: 'my-style-switcher',
list: 'my-style-list',
item: 'my-style-item',
itemSelected: 'my-style-item-selected',
itemHideLabel: 'my-style-item-hide-label',
dark: 'my-style-dark',
light: 'my-style-light',
},
});Contributing
We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup
Clone the repository
git clone https://github.com/muimsd/map-gl-style-switcher cd map-gl-style-switcherInstall dependencies
npm installStart development server
npm run devMake your changes
- Follow TypeScript best practices
- Maintain backward compatibility when possible
- Add tests for new features
Test your changes
npm run validate # Runs type-check, lint, format-check, and testsSubmit a pull request
Guidelines
- Use npm for dependency management
- Follow TypeScript best practices
- Maintain backward compatibility when possible
- Add tests for new features
- Update documentation as needed
- Follow the existing code style
- Ensure all checks pass:
npm run validate
License
MIT License - see LICENSE file for details.
Made with ❤️ for the MapLibre GL and Mapbox GL community
