The theme is set before first paint.
Light, dark and system color modes for React apps. A blocking script in <head> puts the dark class on <html> from the stored mode, and the useColorMode hook reads and changes it.
systemis the default. It resolves against the OSprefers-color-schemeand follows it live.- The choice is stored in
localStorage. - You style the
darkclass with plain CSS. - Every change dispatches a
CustomEvent, so code outside React can follow it too.
pnpm add @variablelab/theme-toggleIt needs React 18 or later.
theme-toggle ships two pieces that work together.
Load the prebuilt script as the first thing in <head>, blocking, so the dark class is on <html> before the page paints.
<head>
<script src="https://unpkg.com/@variablelab/theme-toggle/dist/initial.js"></script>
<!-- ...rest of head... -->
</head>On load it reads the stored mode, or system when there is none, and toggles the dark class on <html>, resolving system against the OS preference. Then it stores the mode and exposes its runtime contract on window.__COLOR_MODE__.
import { useColorMode } from "@variablelab/theme-toggle";
function ThemeSwitch() {
const { colorMode, setColorMode } = useColorMode();
// colorMode -> "light" | "dark" | "system"
return (
<select value={colorMode} onChange={(e) => setColorMode(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="system">System</option>
</select>
);
}Calling setColorMode stores the choice in localStorage, toggles the dark class on <html> and dispatches the change event. While colorMode is system, the hook also follows the OS preference as it changes.
Style the two themes with the dark class on <html>:
:root {
--bg: white;
--fg: black;
}
.dark {
--bg: black;
--fg: white;
}
body {
background: var(--bg);
color: var(--fg);
}| Concern | Value |
|---|---|
| DOM class | dark on document.documentElement |
| Storage key | @variablelab/theme-toggle.colorMode |
| Change event | @variablelab/theme-toggle.colorModeChange |
The change event is a CustomEvent dispatched on document, with the new value in event.detail.colorMode:
document.addEventListener("@variablelab/theme-toggle.colorModeChange", (event) => {
console.log(event.detail.colorMode); // "light" | "dark" | "system"
});The inline script also publishes these names on window.__COLOR_MODE__ (STORAGE_KEY, CUSTOM_EVENT, COLOR_MODE), so other scripts can read them without importing the package.
The package was renamed from @vlandoss/theme-toggle to @variablelab/theme-toggle in 2.0.0:
- Replace the dependency and imports with
pnpm remove @vlandoss/theme-toggle && pnpm add @variablelab/theme-toggle, and change the inline script URL tohttps://unpkg.com/@variablelab/theme-toggle/dist/initial.js. - Listeners of the change event must use the new name,
@variablelab/theme-toggle.colorModeChange, or read it fromwindow.__COLOR_MODE__.CUSTOM_EVENT.
Saved preferences carry over. On init, if the old @vlandoss/theme-toggle.colorMode key exists, its value is copied to the new key, unless the new key already holds a value: then that value wins. The old key is removed either way.
The contributing guide covers issues, pull requests and releases, and the development guide covers setting up the repo and running its checks.
The code is MIT. The Sign in brand/ is not: its use is set by BRAND-USE.md.