# Theme Switch

Style your site in dark mode with ready made Preline UI's dark mode toggle plugin.

[![npm](https://img.shields.io/badge/npm-v4.2.0-blue)](https://www.npmjs.com/package/@preline/theme-switch) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-online-brightgreen)](https://preline.co/plugins/theme-switch.html)

## Contents

- [Overview](#overview)
- [Installation](#installation)
- [Toggling dark mode manually](#toggling-dark-mode-manually)
- [Basic usage](#basic-usage)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)

## Overview

The Theme Switch component provides functionality to toggle between light and dark themes. It manages theme state, saves preferences to localStorage, and integrates with Tailwind CSS dark mode functionality.

**Key Features:**
- Light/dark theme switching
- System theme detection
- LocalStorage persistence
- Tailwind CSS integration
- Programmatic control via JavaScript API
- Event system for theme changes

## Installation

To get started, install Theme Switch plugin via npm, else you can skip this step if you are already using Preline UI as a package.

```bash
npm i @preline/theme-switch
```

### CSS

Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.

```css
@import "tailwindcss";

/* @preline/theme-switch */
@source "../node_modules/@preline/theme-switch/*.js";
@import "./node_modules/@preline/theme-switch/variants.css";
@import "./node_modules/@preline/theme-switch/theme.css";
```

### JavaScript

Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:

```html
<script src="./node_modules/@preline/theme-switch/index.js"></script>
```

### Manual Initialization

Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.

```html
<script type="module">
  import HSThemeSwitch from "@preline/theme-switch/non-auto.mjs";

  new HSThemeSwitch(document.querySelector("#theme-switch"));
</script>
```

### Via Bundler

When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module.

`@preline/theme-switch` is the auto-init entry: it scans the DOM and initializes matching elements automatically.

```js
import "@preline/theme-switch";
```

`@preline/theme-switch/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.

```js
import HSThemeSwitch from "@preline/theme-switch/non-auto";

HSThemeSwitch.autoInit();

// Or initialize a specific element manually
const el = document.querySelector("#theme-switch");
if (el) new HSThemeSwitch(el);
```

### TypeScript

This package ships with TypeScript type definitions. No additional `@types/` package is needed.

## Basic usage

The following example demonstrates the minimal HTML structure required for a theme switch component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. Clicking the button toggles between light and dark themes.

```html
<button type="button" class="hs-dark-mode hs-dark-mode-active:hidden block" data-hs-theme-click-value="dark">
  Dark
</button>
<button type="button" class="hs-dark-mode hs-dark-mode-active:inline-flex hidden" data-hs-theme-click-value="light">
  Light
</button>
```

**Structure Requirements:**
- `data-hs-theme-click-value`: Required on button elements, specifies the theme value (`'default'`, `'dark'`, or `'auto'`)
- `hs-dark-mode`: Required class for theme-aware styling
- Buttons can be separate elements or combined into one toggle

**Initial State:**
- Theme is determined by system preference or saved preference
- Appropriate button is shown/hidden based on current theme

## Configuration Options

### Data Options

Data options are specified via data attributes.

| Attribute | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `data-hs-theme-click-value` | Button element | `'default'` \| `'dark'` \| `'auto'` | `'default'` | When you click on an element with this attribute, the theme changes to the one specified in the attribute. Should be added to the button (trigger). |
| `data-hs-theme-switch` | Input element (checkbox/radio) | - | - | When you change an element with this attribute, the theme changes to the opposite one. Element should have change event, e.g. checkbox or radio button. Should be added to the input (checkbox). |

**Example:**
```html
<!-- Click-based toggle -->
<button data-hs-theme-click-value="dark">Dark Mode</button>

<!-- Change-based toggle -->
<input type="checkbox" data-hs-theme-switch>
```

### Tailwind Modifiers

| Name | Description |
| --- | --- |
| `hs-dark-mode-active:*` | Defines CSS classes when dark mode is ON |
| `hs-default-mode-active:*` | Defines CSS classes when default (light) mode is ON |
| `hs-default-auto-active:*` | Defines CSS classes according to the System theme |

## JavaScript API

The `HSThemeSwitch` object is available in the global `window` object after the plugin is loaded.

### Instance Methods

These methods are called on a theme switch instance.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `setAppearance(theme, isSaveToLocalStorage, isSetDispatchEvent)` | `theme`: `string` (optional, `'dark'` \| `'light'` \| `'default'`)<br>`isSaveToLocalStorage`: `boolean` (optional)<br>`isSetDispatchEvent`: `boolean` (optional) | `void` | Sets the appearance/theme programmatically. `theme` specifies the theme to set. `isSaveToLocalStorage` determines whether to save to localStorage (default: `true`). `isSetDispatchEvent` determines whether to dispatch the theme change event (default: `true`). |
| `destroy()` | None | `void` | Destroys the theme switch instance, removes all generated markup, classes, and event listeners. Use when removing theme switch from DOM. |

### Static Methods

These methods are called directly on the `HSThemeSwitch` class.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSThemeSwitch.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HTMLElement \| { id: string \| number, element: HSThemeSwitch } \| null` | Returns the theme switch instance or element associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSThemeSwitch` instance. If `isInstance` is `false` or omitted, returns the DOM element (`HTMLElement`). Returns `null` if theme switch instance is not found. |

### Usage Examples

**Example 1: Setting theme programmatically**
```javascript
// Get the theme switch instance
const instance = HSThemeSwitch.getInstance('[data-hs-theme-click-value]', true);

if (instance) {
  const { element } = instance;

  // Set dark theme
  element.setAppearance('dark');

  // Set light theme
  element.setAppearance('light');

  // Set theme without saving to localStorage
  element.setAppearance('dark', false);
}
```

**Example 2: Getting instance and accessing properties**
```javascript
// Get the theme switch instance
const instance = HSThemeSwitch.getInstance('[data-hs-theme-click-value]', true);

if (instance) {
  const { element } = instance;

  // Access instance properties
  console.log('Current theme:', element.theme);
  console.log('Type:', element.type);

  // Clean up when removing from DOM
  function removeThemeSwitch() {
    element.destroy();
  }
}
```

**Example 3: Destroying theme switch instance**
```javascript
const instance = HSThemeSwitch.getInstance('[data-hs-theme-click-value]', true);

if (instance) {
  const { element } = instance;
  const removeBtn = document.querySelector('#hs-remove-btn');

  removeBtn.addEventListener('click', () => {
    // Clean up before removing from DOM
    element.destroy();
    // Now safe to remove the element
    document.querySelector('[data-hs-theme-click-value]').remove();
  });
}
```

## Events

Theme switch instances emit global events that can be listened to for theme change tracking.

| Event Name | When Fired | Callback Parameter | Description |
| --- | --- | --- | --- |
| `on-hs-appearance-change` | Every time the mode changes | None | An event that fires every time the mode changes. This is a global window event, not an instance event. |

### Event Usage Example

```javascript
// Listen to global theme change event
window.addEventListener('on-hs-appearance-change', () => {
  console.log('Theme changed!');
  // Perform actions after theme changes
  // e.g., update UI, reload resources, track analytics
});
```

## Common Patterns

### Pattern 1: Click-based Toggle

Use buttons to toggle between themes.

```html
<button data-hs-theme-click-value="dark">Dark</button>
<button data-hs-theme-click-value="light">Light</button>
```

### Pattern 2: Checkbox Toggle

Use a checkbox to toggle theme.

```html
<input type="checkbox" data-hs-theme-switch>
<label>Dark Mode</label>
```

### Pattern 3: Programmatic Control

Control theme programmatically.

```html
<button id="hs-dark-btn">Dark</button>
<button id="hs-light-btn">Light</button>

<script>
  const instance = HSThemeSwitch.getInstance('[data-hs-theme-click-value]', true);
  
  if (instance) {
    const { element } = instance;

    document.querySelector('#hs-dark-btn').addEventListener('click', () => {
      element.setAppearance('dark');
    });

    document.querySelector('#hs-light-btn').addEventListener('click', () => {
      element.setAppearance('light');
    });
  }
</script>
```

## License

Copyright (c) 2026 Preline Labs.

Licensed under the [MIT License](https://opensource.org/licenses/MIT).
