Theme toggle

Switches between light and dark schemes. The icon changes and the choice persists locally.

Sun

Paper / black

The choice stays. The grid does not move.

Use it for

  • Letting the reader pick light or dark and remembering it.
  • Top-right of the page chrome; rs-theme-toggle-inline for a spot in the flow.

Not for

  • Pages that should follow the system only; leave it out and the system preference applies.

Three ways in. They share one source, so the pixels match whichever you pick.

1. Import the package

npm install @noorddev/vlak-react

// once, next to your app's root
import "@noorddev/vlak-react/css";

import { ThemeToggle } from "@noorddev/vlak-react";

Precompiled React and one stylesheet. No compiler to configure. Per-component imports work too: @noorddev/vlak-react/components/theme-toggle.

2. Vendor the source

npx @noorddev/vlak-cli add theme-toggle

The StyleX leaf lands in components/vlak/, for your own compiler to own. See StyleX.

3. Through shadcn

npx shadcn add https://vlak.dev/r/theme-toggle.json

The same registry item, installed by shadcn's CLI.

CSS only

<link rel="stylesheet" href="node_modules/@noorddev/vlak/css/vlak.css" />

No React. Link vlak.css and use the markup and classes below.

import { ThemeToggle } from "@noorddev/vlak-react";

<ThemeToggle storageKey="vlak-theme" onThemeChange={(dark) => track(dark)} />

ThemeToggle

One mark. Moon on paper, sun on black. The button sets data-theme="dark" on <html> and persists the choice. Apps pin it top-right; catalog and previews use the inline modifier. The name states the action ("Switch to dark scheme"), so it changes with the state.

PropTypeDefaultDescription
storageKeystring"vlak-theme"localStorage key the choice persists under.
onThemeChange(dark: boolean) => void

Also accepts Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange">.

KeysDoes
TabMoves focus to the button
Enter, SpaceSwitches the scheme
<button class="rs-theme-toggle rs-theme-toggle-inline" aria-label="Toggle color scheme"><svg class="rs-theme-moon" viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M10.5 3.5 A5.5 5.5 0 1 0 10.5 12.5 A4 4 0 1 1 10.5 3.5" vector-effect="non-scaling-stroke"/></svg></button>
.rs-theme-toggle.rs-theme-toggle-inline.rs-theme-sun.rs-theme-moon