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

browserux-theme-switcher

v1.1.0

Published

Lightweight and customizable Web Component for theme toggling (light/dark), with i18n, accessibility, and full framework compatibility.

Downloads

19

Readme

EN | FR

BrowserUX Theme Switcher

A lightweight and customizable Web Component that allows users to toggle between light and dark themes. Accessible, internationalized, and compatible with all modern frameworks.

npm version unpkg

Table of Contents

Features

🎛 Smart Behavior

  • 🎚 Theme Switching
    Toggles between data-theme="light" and "dark" on a target element (default is <html>)

  • 💾 Automatic Persistence
    Stores user preference in localStorage and restores it on each visit

  • 🕶 System Detection
    Automatically applies the theme based on prefers-color-scheme if no user preference is set

  • 📢 theme-change Event
    Fires a custom event on each theme change (e.detail.theme = "light" | "dark")

🌍 Accessibility & Internationalization

  • 🧠 Dynamic ARIA Labels
    Multilingual accessible labels generated automatically or customizable (data-label-*)

  • 🌐 Internationalization (lang)
    Supports multiple languages (auto-detection or manual setting via the lang attribute)

🎨 Visual Customization

  • 🎯 CSS Targeting (target)
    Allows applying the theme to a specific element (e.g., <main>, #app, etc.)

  • 🎨 Customizable CSS Variables
    Extensive visual customization via CSS properties (--bux-switch-*)

  • 🌗 Custom Icon Slots
    Customize icons using SVGs, emojis, or images (light-icon, dark-icon)

  • 🖼 Adaptive Images (.has-dark)
    Automatically switch images based on the theme (e.g., logo.pnglogo-dark.png)

🔧 Flexible Integration

  • 🧩 Optional Shadow DOM (no-shadow)
    Disable encapsulation to allow more flexible global styling

How It Works

The <browserux-theme-switcher> component dynamically applies a light or dark theme to an element on your page (<html> by default, or another element via the target attribute).

It follows a three-step logic:

1. Automatic Detection of System Theme

If no user preference is set, the component automatically detects the system’s preferred theme using the CSS rule:

@media (prefers-color-scheme: dark)

2. Storing User Preference

When the user clicks the button to switch themes, their preference (light or dark) is saved in localStorage.

This preference will:

  • be automatically applied on the next visit
  • take priority over system detection

3. Applying the Theme in the DOM

The component dynamically sets or updates the data-theme attribute on the targeted element, for example:

<html data-theme="dark">...</html>

This allows you to:

  • style the page using conditional CSS variables
  • adapt images (using .has-dark)
  • respond to events (like theme-change)

The component works without dependencies, requires no complex configuration, and is compatible with all modern frameworks (React, Vue, Angular) as well as plain HTML.

Installation

npm install browserux-theme-switcher

Or via CDN:

<script type="module" src="https://unpkg.com/browserux-theme-switcher/dist/browserux-theme-switcher.min.js"></script>

Use the .esm.js version if you're integrating this component via a bundler (React, Vue, etc.), and the .min.js version for direct HTML integration via CDN.

Usage

The <browserux-theme-switcher> Web Component

Modern project with bundler (Vite, Webpack, etc.)

  1. Simply import the component into your code:
import 'browserux-theme-switcher';
  1. Then use it in your HTML:
<browserux-theme-switcher></browserux-theme-switcher>

React / Next.js

  1. Add this to your React component (typically inside a useEffect):
import { useEffect } from 'react';

useEffect(() => {
    import('browserux-theme-switcher');
}, []);
  1. And in your JSX:
<browserux-theme-switcher></browserux-theme-switcher>

Add the types/browserux-theme-switcher.d.ts file for better TypeScript support with JSX.

Vue 3

  1. Add this in main.js or main.ts:
import 'browserux-theme-switcher';
  1. Use it as a native component:
<browserux-theme-switcher lang="fr"></browserux-theme-switcher>

Angular

  1. Import it in main.ts:
import 'browserux-theme-switcher';
  1. Add CUSTOM_ELEMENTS_SCHEMA to AppModule:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}

Integration without bundler / global script

  1. Add the component directly via a CDN:
<script type="module" src="https://unpkg.com/browserux-theme-switcher/dist/browserux-theme-switcher.min.js"></script>
  1. And:
<browserux-theme-switcher></browserux-theme-switcher>

Managing CSS Styles

To apply the light or dark theme to your page, you need to define your colors using CSS variables.
The <browserux-theme-switcher> component automatically applies a data-theme="dark" or "light" attribute
to the targeted element (by default <html>), which enables dynamic styling of your interface.

Full Example

:root {
    --bux-page-bg: #eaeaea;
    --bux-page-color: #121212;
    --bux-color-primary: #f05e0e;
    --bux-color-secondary: #0e93f0;
    --bux-white: #fff;
}

/** Automatic dark mode based on system preferences */
@media (prefers-color-scheme: dark) {
    :root {
        --bux-page-bg: #333;
        --bux-page-color: #eaeaea;
        --bux-color-primary: #eb8a55;
        --bux-color-secondary: #58aae3;
        --bux-white: #444;
    }
}

/** Dark mode forced via browserux-theme-switcher */
[data-theme="dark"] {
    --bux-page-bg: #333;
    --bux-page-color: #eaeaea;
    --bux-color-primary: #eb8a55;
    --bux-color-secondary: #58aae3;
    --bux-white: #444;
}

Explications

  • :root defines the default colors (light mode).
  • @media (prefers-color-scheme: dark) takes system preferences into account if the user hasn't selected a theme yet.
  • [data-theme="dark"] forces dark mode when the user clicks the browserux-theme-switcher button.

The switcher applies data-theme="dark" or data-theme="light" to the targeted element (html by default, or a container via the target attribute).
You should apply CSS variables to that same element or a shared parent.

Managing Images Based on Theme (Dark Mode)

The <browserux-theme-switcher> component provides automatic support for theme-aware images using the has-dark class.

Automatic image switching (filename convention)

By default, any <img> element with the has-dark class will have its src swapped based on the current theme:

<img src="logo.png" class="has-dark" alt="Logo">
  • In light mode: the original image is used (/img/logo.png)
  • In dark mode: the component will look for /img/logo-dark.png

This works by appending -dark before the file extension, and is ideal for static assets that follow a consistent naming convention.

Manual image switching (custom source attributes)

For more control, you can specify exactly which image to use for each theme using data-src-light and data-src-dark:

<img
  class="has-dark"
  src="/cdn/images/logo-light.webp"
  data-src-light="/cdn/images/logo-light.webp"
  data-src-dark="/cdn/images/logo-dark.webp"
  alt="Logo"
/>

This is useful when:

  • Your images don't follow the -dark naming pattern
  • Assets are served from a CMS or CDN with custom URLs
  • You want to override the automatic logic for specific cases

If both data-src-* attributes are present, they take precedence over the filename convention.

Opting out of auto-swapping

You can disable image swapping for a specific image by adding the data-locked attribute:

<img
  class="has-dark"
  src="/img/logo-custom.webp"
  data-locked
  alt="Static Logo"
/>

This will prevent the component from modifying the src, regardless of the theme.

These features work across all modern frameworks, including React, Vue, Angular, and plain HTML.

Parameters of <browserux-theme-switcher>

<browserux-theme-switcher> offers many customization options:

| Parameter | Type | Name | Description | |--------------------------|-----------|------------------|-----------------------------------------------------| | Custom Targeting | Attribute | target | Applies the theme to a specific element | | Internationalization | Attribute | lang | Language selection | | ARIA Accessibility | Attribute | data-label-* | Customizable accessible labels | | Optional Shadow DOM | Attribute | no-shadow | Disable encapsulation | | CSS Customization | Attribute | style | Customization via CSS variables | | Custom Event | Event | theme-change | Event triggered on every theme change | | Icon Slots | Slot | *-icon | Icon customization |

Attributes

Custom Targeting (target)

By default, the <browserux-theme-switcher> component applies the theme (data-theme="light" or "dark") to the <html> element.
However, you can customize this target using the target attribute.

Attribute: target
  • Type: string (valid CSS selector)
  • Default value: html
  • Effect: applies the data-theme attribute to the specified element
Example
<browserux-theme-switcher 
    target="#app"
></browserux-theme-switcher>

<div id="app">
  <!-- The theme is applied here -->
</div>

In this example, the #app element (and not <html>) will receive the data-theme attribute.
This allows you to scope the theme to a specific container in your application—useful for micro-frontends, app shells, or embedded widgets.

Tip

Make sure your CSS styles are based on [data-theme="dark"] or [data-theme="light"] applied to the correct selector:

#app[data-theme="dark"] {
  --bux-page-bg: #333;
  /* etc. */
}

If the selector passed to target does not match any element at render time, the fallback will be <html>.

Internationalization (lang)

The <browserux-theme-switcher> component supports multiple languages for its accessible labels (e.g., "Switch to dark mode", "Activer sombre", etc.).

Attribute: lang
  • Type: string ("en", "fr", "es", "de", "ja", "ru", "pt", "it", "nl")
  • Default value: auto-detection
  • Effect: forces the language used for the switch's ARIA labels (aria-label)
Example
<browserux-theme-switcher 
  lang="fr"
></browserux-theme-switcher>

The ARIA label of the button will automatically be in French:
aria-label="Activer le mode sombre" or aria-label="Activer le mode clair"

Automatic Detection if lang Is Not Defined

If you don’t specify the lang attribute, the component follows this logic:

  1. Uses the lang value on the <browserux-theme-switcher> tag
  2. Otherwise, checks the lang value on the <html lang="..."> tag
  3. Otherwise, falls back to "en" (English)
Built-in Languages

The component supports the following languages for accessible labels (aria-label):

  • 🇬🇧 en – English (default)
  • 🇫🇷 fr – French
  • 🇪🇸 es – Spanish
  • 🇩🇪 de – German
  • 🇯🇵 ja – Japanese
  • 🇷🇺 ru – Russian
  • 🇵🇹 pt – Portuguese
  • 🇮🇹 it – Italian
  • 🇳🇱 nl – Dutch

ARIA Accessibility (data-label-light / data-label-dark)

The <browserux-theme-switcher> component is designed to be screen reader–friendly,
thanks to dynamic aria-labels that describe the button's action (e.g., switch to light or dark mode).

By default, these labels are automatically generated based on the selected language (lang attribute).
However, you can override them with your own custom labels using two attributes:

Attributes

| Attribute | Role | |---------------------|----------------------------------------------------------------------------------------| | data-label-light | Label when dark theme is active and the button allows switching to light mode | | data-label-dark | Label when light theme is active and the button allows switching to dark mode |

Example
<browserux-theme-switcher
    data-label-light="Activer le thème clair"
    data-label-dark="Passer en mode sombre">
</browserux-theme-switcher>

Result:

  • In light mode → aria-label="Switch to dark mode"
  • In dark mode → aria-label="Activate light theme"

These attributes take precedence over automatic language detection (lang).

Optional Shadow DOM (no-shadow)

By default, the <browserux-theme-switcher> component uses Shadow DOM to encapsulate its HTML and CSS.
This ensures that its internal styles don’t interfere with the rest of the page—and vice versa.

However, in some cases—such as applying global styles or addressing specific framework constraints—it may be helpful to disable this encapsulation.

Attribute: no-shadow
  • Type: boolean (presence-only)
  • Default value: not present → Shadow DOM enabled
  • Effect: if present, the component is rendered in the global DOM without encapsulation
Example
<browserux-theme-switcher no-shadow></browserux-theme-switcher>

This component:

  • will be rendered in the regular DOM (not inside a shadowRoot)
  • can be styled using your global CSS
  • will more easily inherit external styles
When to use no-shadow?
  • When you want to easily override the component’s styles via global CSS
  • If you need to theme the switcher using page-level CSS variables
  • When integrating in a framework (e.g., Angular) where Shadow DOM causes issues
  • To debug the component’s rendering more easily in the DOM

⚠️ Without Shadow DOM, the component is more vulnerable to global style conflicts. Use with caution in large-scale applications.

CSS Customization (style)

The <browserux-theme-switcher> component exposes several customizable CSS variables
to let you tweak its appearance without overriding internal styles.

Available Variables

| Variable | Default | Description | |---------------------------|-----------|----------------------------| | --bux-switch-width | 40px | Width of the toggle button | | --bux-switch-height | 24px | Height of the toggle button | | --bux-switch-bg-color | #888 | Background color of the switch | | --bux-switch-thumb-color| #fff | Thumb color | | --bux-switch-emoji-size | inherit | Emoji icon size |

Example
<browserux-theme-switcher
    style="
        --bux-switch-width: 60px;
        --bux-switch-height: 32px;
        --bux-switch-bg-color: #222;
        --bux-switch-thumb-color: orange;
        --bux-switch-emoji-size: 1.5rem;"
></browserux-theme-switcher>
  • These variables can be dynamically updated based on the theme ([data-theme="dark"]) or breakpoints (media queries).
  • They work even if Shadow DOM is enabled, thanks to the use of CSS custom properties.

Events

Custom Event (theme-change)

The <browserux-theme-switcher> component dispatches a custom event named theme-change whenever the theme changes
(e.g., after a user click, or an initial load using localStorage, etc.).

This event allows your application to dynamically respond to theme changes (layout updates, analytics, etc.).

Event
  • Name: theme-change
  • Payload: the emitted event is a CustomEvent where e.detail.theme contains the new theme value ("light" or "dark").
Example JavaScript listener
const switcher = document.querySelector('browserux-theme-switcher');

switcher?.addEventListener('theme-change', (e) => {
  console.log('Thème sélectionné :', e.detail.theme);
});
Possible use cases
  • Dynamically change a CSS class on the body
  • Trigger an animation or transition
  • Store the theme in a global context or JS service
  • Track interactions with an analytics tool

The event is available as soon as the component is initialized and works in all contexts (frameworks or plain HTML).

Slots

Custom icon slots (light-icon / dark-icon)

The <browserux-theme-switcher> component allows customization of its toggle button appearance
by replacing default icons using HTML slots.

Available slots

| Slot | Displayed when the current theme is... | Example usage | |---------------|-----------------------------------------------------|----------------------------| | light-icon | Active = dark (icon to switch to light mode) | ☀️, sun, light.svg | | dark-icon | Active = light (icon to switch to dark mode) | 🌙, moon, moon.svg |

Examples
<browserux-theme-switcher>
  <span slot="light-icon">🔆</span>
  <span slot="dark-icon">🌑</span>
</browserux-theme-switcher>

Or with SVG images:

<browserux-theme-switcher>
  <img slot="light-icon" src="sun.svg" width="20" height="20" alt="Light mode">
  <img slot="dark-icon" src="moon.svg" width="20" height="20" alt="Dark mode">
</browserux-theme-switcher>
Behavior
  • Each slot is dynamically shown or hidden based on the active theme
  • Slots are accessible (with aria-label) and can contain:
    • emojis
    • SVG icons
    • raster images
    • custom elements

If no slot is provided, default icons are used (☀️ / 🌙).

Build & Development

npm install
npm run build

The project uses TypeScript and Rollup to generate build outputs:

  • dist/browserux-theme-switcher.esm.js
  • dist/browserux-theme-switcher.umd.js
  • dist/browserux-theme-switcher.d.ts

These builds are ready to be used in both module-based environments and traditional script loading contexts.

License

MIT License, Free to use, modify, and distribute.