npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

map-gl-style-switcher

v0.9.1

Published

A customizable style switcher control for Mapbox GL JS and MapLibre GL JS

Readme

Map GL Style Switcher

npm version CI License: MIT Netlify Status

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 GL
  • MapGLStyleSwitcher - React component for react-map-gl integration Live demo of the style switcher control in action

🌐 Live Demo

Animated Demo

Demo GIF

Available Styles

Available Styles

Features

  • IControl implementation for Mapbox GL / MapLibre GL
  • React component for react-map-gl integration
  • 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
  • RTL text support for Arabic scripts
  • Configurable display options (show/hide labels and images)
  • Callbacks for before/after style change
  • Fully customizable CSS classe's
  • TypeScript support
  • Accessibility features (ARIA labels, keyboard navigation)
  • 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-switcher

Basic Map Instance

For React applications where you manage the map instance yourself (e.g., with useEffect), use the useStyleSwitcher hook:

import React, { useEffect, useRef } from 'react';
import * as maplibregl from 'maplibre-gl';
import { StyleSwitcherControl, type StyleItem } from 'map-gl-style-switcher';
// Custom hook
// import { useStyleSwitcher } 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 mapStyles: 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',
  },
  {
    id: 'dark-matter',
    name: 'Dark Matter',
    image:
      'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/dark.png',
    styleUrl: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
    description: 'Dark style from Carto',
  },
  {
    id: 'arcgis-hybrid',
    name: 'ArcGIS Hybrid',
    image:
      'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/arcgis-hybrid.png',
    styleUrl:
      'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/arcgis_hybrid.json',
    description: 'Hybrid Satellite style from ESRI',
  },
  {
    id: 'osm',
    name: 'OSM',
    image:
      'https://raw.githubusercontent.com/muimsd/map-gl-style-switcher/refs/heads/main/public/osm.png',
    styleUrl:
      'https://raw.githubusercontent.com/go2garret/maps/main/src/assets/json/openStreetMap.json',
    description: 'OSM style',
  },
];

export default function App() {
  const mapContainer = useRef<HTMLDivElement | null>(null)

  useEffect(() => {
    if (!mapContainer.current) {
      console.error('Map container not found!');
      return;
    }
    const currentStyle = mapStyles[0];
    const map = new maplibregl.Map({
      container: mapContainer.current,
      // Use a simple, reliable style first
      style: currentStyle.styleUrl,
      // Center on Dubai, UAE
      center: [55.2708, 25.2048],
      zoom: 10,
    });

    // Add style switcher control
    const styleSwitcher = new StyleSwitcherControl({
      styles: mapStyles,
      theme: 'auto',
      showLabels: true,
      showImages: true,
      activeStyleId: currentStyle.id,
      onBeforeStyleChange: (from, to) => {
        console.log('Changing style from', from.name, 'to', to.name);
      },
      onAfterStyleChange: (_from, to) => {
        map.setStyle(to.styleUrl);
        console.log('Style changed to', to.name);
      },
    });
    map.addControl(styleSwitcher, 'bottom-left');

    return () => {
      map.remove();
    };
  }, []); // Empty dependency array

  // Add style switcher control to the map
  // useStyleSwitcher(map.current, {
  //   styles,
  //   theme: 'auto',
  //   position: 'top-right',
  //   onAfterStyleChange: (from, to) => {
  //     if (map.current) {
  //       map.current.setStyle(to.styleUrl);
  //     }
  //   },
  // });

  return (
    <div
      ref={mapContainer}
      style={{
        width: '100%',
        height: '500px',
      }}
    />
  );
}

Examples

React Map GL Example

View the maplibre example →

React Map GL Example

View the react-map-gl example →

Configuration Options

interface StyleSwitcherControlOptions {
  styles: StyleItem[]; // Array of map styles (required)
  activeStyleId?: string; // Currently active style ID (default: first style)
  onBeforeStyleChange?: (from: StyleItem, to: StyleItem) => void; // Callback before style change
  onAfterStyleChange?: (from: StyleItem, to: StyleItem) => void; // Callback after style change
  showLabels?: boolean; // Show style names (default: true)
  showImages?: boolean; // Show style thumbnails (default: true)
  animationDuration?: number; // Animation duration in ms (default: 200)
  maxHeight?: number; // Max height of expanded list (default: 300)
  theme?: 'light' | 'dark' | 'auto'; // UI theme (default: 'light')
  classNames?: Partial<StyleSwitcherClassNames>; // Custom CSS classes
  rtl?: boolean; // Enable 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 style item class
  itemHideLabel: string; // Hide label utility class
  dark: string; // Dark theme class
  light: string; // Light theme class
}

Example of different map styles that can be used with the style switcher

Option Details

  • activeStyleId: Controls both the initially selected style and what's displayed in the collapsed state
  • showLabels & showImages: At least one must be true
  • theme:
    • 'light': Light color scheme
    • 'dark': Dark color scheme
    • 'auto': Auto-detect from system preference
  • rtl: Enables right-to-left layout for Arabic/Hebrew interfaces

Customizing CSS Classes

You can override all CSS classes used by the style switcher control using the classNames option:

import { StyleSwitcherControl } from 'map-gl-style-switcher';

const styleSwitcher = new StyleSwitcherControl({
  styles,
  showLabels: true,
  showImages: true,
  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',
  },
});

See the default class names in the StyleSwitcherControl source for all available keys.

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

  1. Clone the repository

    git clone https://github.com/muimsd/map-gl-style-switcher
    cd map-gl-style-switcher
  2. Install dependencies

    npm install
  3. Start development server

    npm run dev
  4. Make your changes

    • Follow TypeScript best practices
    • Maintain backward compatibility when possible
    • Add tests for new features
  5. Test your changes

    npm run validate  # Runs type-check, lint, format-check, and tests
  6. Submit 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.

Author

Muhammad Imran Siddique

Repository


Made with ❤️ for the MapLibre GL and Mapbox GL community