Guide
Getting started
Two steps, and the second one is the important one.
1. The control
npm install @devix-labs/theme-toggle
import { createThemeToggle } from '@devix-labs/theme-toggle';
import '@devix-labs/theme-toggle/styles.css';
createThemeToggle(document.querySelector('#theme'));
That renders a three-way switch — light, dark, system — as a group of real radios, so the arrow keys work and a screen reader announces "2 of 3".
2. The snippet that stops the flash
A page rendered on a server does not know what the visitor chose, so it paints the default and corrects itself a moment later. For anyone using a dark theme that is a white flash on every page load.
The only fix is a blocking script in <head>, before the first paint:
<head>
<script>
// 564 bytes, from antiFlashScript()
</script>
<link rel="stylesheet" href="/app.css">
</head>
Generate it once, at build time or in your template:
import { antiFlashScript } from '@devix-labs/theme-toggle';
antiFlashScript(); // the body, as a string
antiFlashScript({ tag: true }); // wrapped in <script>
antiFlashScript({ tag: true, nonce }); // with a CSP nonce
{{-- Laravel --}}
<head>
{!! \Devix\antiFlash() !!}
</head>
---
import { antiFlashScript } from '@devix-labs/theme-toggle';
---
<head><script is:inline set:html={antiFlashScript()} /></head>
Why this is a string
Because in React it cannot be anything else without trouble. The two most-reacted open issues on the library with nineteen million weekly downloads — seventy-five reactions between them — are that its anti-flash script breaks when React renders it.
A string has no such problem. It is not a component, it does not hydrate, and it runs before your bundle is even fetched.
Making your CSS follow it
By default the theme is written as a class on <html>:
:root { --bg: #ffffff; --fg: #18181b; }
.dark { --bg: #101014; --fg: #ededf0; }
Tailwind's dark: variant works with no configuration. For Bootstrap, or
anything using data-theme, say so:
createThemeToggle(el, { attribute: ['class', 'data-bs-theme'] });
Both are applied, which is what a page using two design systems needs — and is still an open request on the alternative.
The theme without the control
import { createTheme } from '@devix-labs/theme-toggle';
const theme = createTheme({ themeColor: { light: '#ffffff', dark: '#101014' } });
theme.get(); // 'system' — what was chosen
theme.resolved(); // 'dark' — what that means now
theme.set('light');
theme.toggle(); // light → dark → system → light
theme.subscribe((choice, resolved) => {});
Nothing touches the document at import time, so importing this on a server is safe.