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

leaflet-theme-control

v0.1.4

Published

A Leaflet control for switching between visual themes (light, dark, grayscale, custom, etc.) using CSS filters

Readme

Leaflet Theme Control

A Leaflet control for switching between visual themes using CSS filters. Perfect for adding dark mode, grayscale, and custom visual modes to your maps without requiring multiple tile layers.

Leaflet Theme Control Screenshot

Features

  • Multiple themes: Light, Dark, Grayscale, Custom
  • Theme Editor: Customize filters with live preview sliders (optional)
  • Accessibility: Adaptable themes for better visibility
  • CSS Filters: No need for multiple tile sources
  • Persistent: Saves user preference in localStorage
  • System Detection: Automatically detects OS dark mode preference
  • i18n Ready: Customizable labels with auto-update on language change
  • Lightweight: Zero dependencies (except Leaflet)
  • Performance: Instant theme switching without reloading tiles

Installation

As npm package

npm install leaflet-theme-control

With bundler (Webpack, Vite, Rollup):

import { ThemeControl } from "leaflet-theme-control";
import "leaflet-theme-control/src/leaflet-theme-control.css";

Without bundler (plain HTML):

<link rel="stylesheet" href="https://unpkg.com/leaflet-theme-control/src/leaflet-theme-control.css" />

<script type="importmap">
  {
    "imports": {
      "leaflet": "https://unpkg.com/[email protected]/dist/leaflet.js",
      "leaflet-theme-control": "https://unpkg.com/leaflet-theme-control/src/leaflet-theme-control.js"
    }
  }
</script>

<script type="module">
  import { ThemeControl } from "leaflet-theme-control";
  // Your code here
</script>

Usage

Basic Example

import L from "leaflet";
import { ThemeControl } from "leaflet-theme-control";

const map = L.map("map").setView([51.505, -0.09], 13);

L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
  attribution: "© OpenStreetMap contributors"
}).addTo(map);

// Add theme control
new ThemeControl().addTo(map);

With Custom Options

new ThemeControl({
  position: "topright",
  defaultTheme: "light",
  detectSystemTheme: true,
  storageKey: "my-map-theme",

  // Custom label function for i18n
  getLabel: (themeKey) => {
    return i18n.t(`themes.${themeKey}`);
  },

  // Callback when theme changes
  onChange: (themeKey, theme) => {
    console.log(`Theme changed to: ${themeKey}`);
  }
}).addTo(map);

Custom Themes

import { ThemeControl } from "leaflet-theme-control";

new ThemeControl({
  themes: {
    light: {
      label: "Light Mode",
      filter: "",
      icon: "☀️",
      controlStyle: "light",
      className: "theme-light"
    },
    dark: {
      label: "Dark Mode",
      filter: "invert(1) hue-rotate(180deg) saturate(0.6) brightness(0.5)",
      icon: "🌙",
      controlStyle: "dark",
      className: "theme-dark",
      applyToSelectors: [".my-sidebar", ".my-header"] // Apply filter to these elements too
    },
    monochrome: {
      label: "Black & White",
      filter: "grayscale(1) contrast(1.2)",
      icon: "⚫",
      controlStyle: "light",
      className: "theme-mono",
      applyToSelectors: ".my-sidebar" // Single selector also works
    },
    custom: {
      label: "My Theme",
      filter: "invert(1) hue-rotate(180deg) saturate(1) brightness(1) contrast(1) sepia(0.5) grayscale(0.5)",
      icon: "🎨",
      controlStyle: "dark",
      className: "theme-custom",
      applyToSelectors: [".my-sidebar", ".my-footer"]
    }
  }
}).addTo(map);

Theme Properties:

  • filter: CSS filter string (applied to map and applyToSelectors)
  • controlStyle: "light" or "dark" for Leaflet controls styling
  • className: CSS class added to <html> element (for custom styling)
  • applyToSelectors: String or Array of CSS selectors to apply the same filter to

Use Cases:

  • applyToSelectors: Apply the same dark mode filter to sidebar, header, footer etc.
  • className: Style elements differently per theme with CSS
/* Using className for custom styling */
.theme-dark .my-button {
  background: #2d2d2d;
  color: #e0e0e0;
}

/* Elements in applyToSelectors get the filter automatically */
.my-sidebar {
  background: white; /* Will be inverted in dark mode */
}

Programmatic Control (No UI Button)

For advanced use cases where you want to control themes from your own UI:

// Create control without visible button
const themeControl = new ThemeControl({
  addButton: false, // No UI button
  enableEditor: true, // Editor still available programmatically
  onChange: (theme) => {
    console.log("Theme changed:", theme);
  }
});

map.addControl(themeControl);

// Control themes programmatically
themeControl.setTheme("dark");
console.log(themeControl.getCurrentTheme()); // "dark"

// Open editor from custom button
myCustomButton.onclick = () => {
  themeControl.editor.openThemeSelector();
};

See examples/api.html for a complete example.

API

Options

| Option | Type | Default | Description | | ------------------- | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------- | | position | String | "topright" | Position of the control | | themes | Object | DEFAULT_THEMES | Theme definitions | | defaultTheme | String | "light" | Initial theme | | storageKey | String | "leaflet-theme" | localStorage key | | detectSystemTheme | Boolean | true | Detect OS dark mode | | cssSelector | String | ".leaflet-tile-pane" | Elements to apply filter to | | addButton | Boolean | true | Add UI button to map (set to false for programmatic control only) | | enableEditor | Boolean | false | Enable theme editor UI with customization sliders | | onChange | Function | null | Callback on theme change AND editor changes: (themeKey, theme) => {} | | getLabel | Function | null | Function to get translated theme labels: (themeKey) => string (optional if themes have label property) | | getEditorLabels | Function | null | Function to get translated editor UI labels: (key) => string | | panelPosition | String | "topright" | Position of editor panel: "topright", "topleft", "bottomright", "bottomleft" | | panelZIndex | Number | 1000 | Z-index for editor panel to avoid conflicts |

Methods

| Method | Returns | Description | | --------------------- | -------- | ------------------------------------------------- | | setTheme(themeKey) | void | Switch to specific theme | | getCurrentTheme() | String | Get current theme key | | getThemes() | Object | Get all available themes | | updateButtonLabel() | void | Update button label (auto-called on html[lang]) |

Editor API (when enableEditor: true)

| Method | Returns | Description | | ---------------------------------- | ------- | ------------------------------ | | editor.openThemeSelector() | void | Open theme selector panel | | editor.openThemeEditor(themeKey) | void | Open editor for specific theme | | editor.close() | void | Close editor panel |

Built-in Themes

  • Light: Default, no filter
  • Dark: Inverted colors with adjusted hue, saturation, and brightness
  • Grayscale: Black and white for printing or reduced distraction
  • Custom: Fully customizable theme with combined filters (editable via theme editor)

License

MIT License. See LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Credits

Originally developed for the Veggiekarte project. But hopefully useful for others too!