implement
Introduction

Introduction

Dark mode for a site — the visitor's choice, the system preference, and the class on the html element.

Good afternoon

Every color here follows the manager's mode.

rendering · your system prefers

import { Div, P, Span } from "@implementjs/core";
import { MonitorIcon, MoonIcon, SunIcon } from "@implementjs/lucide";
import { createModeManager, type Mode } from "@implementjs/mode-watcher";
import { Button } from "@/lib/components/ui/button";

// A real app mounts `ModeWatcher({ manager })` at the root and lets the manager
// put the class on `<html>`. This demo only reads the manager, so the preview
// below changes instead of the page around it.
const manager = createModeManager({ modeStorageKey: "docs-mode-demo" });

const choices = [
	{ mode: "light", label: "Light", icon: SunIcon },
	{ mode: "system", label: "System", icon: MonitorIcon },
	{ mode: "dark", label: "Dark", icon: MoonIcon },
] satisfies { mode: Mode; label: string; icon: typeof SunIcon }[];

export default function ModeWatcherDemo() {
	return Div(
		{ class: "flex w-full max-w-sm flex-col gap-4" },
		Div(
			{ class: "flex items-center justify-center gap-2" },
			...choices.map(({ mode, label, icon: Icon }) =>
				Button(
					{
						variant: "outline",
						size: "sm",
						"aria-pressed": manager.userPrefersMode.bind((current) => current === mode),
						class: manager.userPrefersMode.bind((current) =>
							current === mode ? "bg-accent text-accent-foreground" : "",
						),
						onClick: () => manager.setMode(mode),
					},
					Icon({ "aria-hidden": true }),
					label,
				),
			),
		),
		Div(
			{
				class: [
					"flex flex-col gap-3 rounded-lg border p-4 transition-colors",
					manager.mode.bind((mode) =>
						mode === "light"
							? "border-neutral-200 bg-white text-neutral-900"
							: "border-neutral-800 bg-neutral-950 text-neutral-50",
					),
				],
			},
			P({ class: "text-sm font-medium" }, "Good afternoon"),
			P(
				{
					class: [
						"text-sm",
						manager.mode.bind((mode) =>
							mode === "light" ? "text-neutral-500" : "text-neutral-400",
						),
					],
				},
				"Every color here follows the manager's mode.",
			),
			Div(
				{ class: "flex gap-2" },
				...["bg-sky-500", "bg-emerald-500", "bg-amber-500"].map((color) =>
					Div({ class: ["size-6 rounded-full", color] }),
				),
			),
		),
		P(
			{ class: "text-center text-xs text-muted-foreground" },
			"rendering ",
			Span(
				{ class: "font-medium" },
				manager.mode.bind((mode) => mode ?? "…"),
			),
			" · your system prefers ",
			Span(
				{ class: "font-medium" },
				manager.systemPrefersMode.bind((mode) => mode ?? "…"),
			),
		),
	);
}

Dark mode is three questions: what did the visitor pick, what does their operating system prefer, and which of the two is the page rendering right now. @implementjs/mode-watcher answers all three. It is a port of mode-watcher — a manager owns the mode, and a component mounts once at the root and keeps <html> in step with it.

Add it next to core:

npm install @implementjs/core @implementjs/mode-watcher
import { App, Button } from "@implementjs/core";
import { createModeManager, ModeWatcher } from "@implementjs/mode-watcher";

export const mode = createModeManager();

App({ target: document.body }).render(
	ModeWatcher({ manager: mode }),
	Button({ onClick: () => mode.toggleMode() }, "Toggle theme"),
);

This site runs on it — the switcher in the header sets the mode, and everything below <html> follows. That is the whole setup. <html> gets class="dark" and style="color-scheme: dark" when the mode is dark, the choice is remembered in localStorage, and the page comes back in the same mode next visit — without the flash of the wrong theme that usually comes with it.

In a kit app, ModeWatcher goes in the root layout so it stays mounted across navigations:

// src/routes/layout.ts
import { Div, Main } from "@implementjs/core";
import { ModeWatcher } from "@implementjs/mode-watcher";
import { mode } from "@/lib/mode";
import type { LayoutProps } from "./$types";

export default function Layout({ children }: LayoutProps) {
	return Div(ModeWatcher({ manager: mode }), Main(children));
}

create-implement-app can do all of this for you — pick the @implementjs/mode-watcher addon and the app it scaffolds opens on a working light/dark toggle.

Where to next

  • The manager covers what owns the mode, and how to read and change it.
  • Styling covers the classes on <html>, themes, and transitions.
  • First paint covers the blocking script that beats the flash.
  • API lists every export.