Why not just write the CSS?
Tokens drift
Hand-written --vars go stale: rename one, grep,
pray. Here a token is a typed key — rename it and every use is a
compile error, not a silently dead variable.
Dark mode is a ritual
Duplicated [data-theme] blocks, a FOUC hack, a JS
listener for the system preference.
light-dark() output needs none of it — the browser
does the switching.
Pipelines are heavy
Style Dictionary wants a build step and a config folder. This is one function call, 2.6 kB gzip, works with any bundler or none.
Palettes for free
Ten shades from one brand color via oklch relative colors — computed by the browser, not shipped in your bundle. Rebrand = change one value.
Start in 30 seconds
One command scaffolds the config and writes the CSS. Everything below on this page is what it automates:
$ npx varth init var(−th) v0.2.0 ✏ varth.config.ts scaffolded ✓ 6 tokens · 2 themes · strategy: light-dark ✎ varth.css 0.81 kB · gzip 0.29 kB ✎ varth.js 1.06 kB · setTheme/getTheme, zero deps ✎ varth.d.ts 0.29 kB · types for the module above light ██ accent ██ bg ██ text ██ muted dark ██ accent ██ bg ██ text ██ muted ⚡ done in 24 ms next steps 1. import "./varth.css" 2. background: var(--th-accent) anywhere 3. import { setTheme } from "./varth.js" typed, persisted, optional
In → out
Left: the actual config of this page. Right: the CSS
varth gen writes — colors folded into
light-dark(), the shadow (not a color) got an automatic
media-query fallback, radius emitted once.
One command → three files
npx varth gen reads the config and writes everything
your app needs. The files are yours: commit them, read them, no
runtime package in your bundle.
:root {
color-scheme: light dark;
--th-accent: light-dark(#3d6fb4, #7fa9e0);
--th-bg: light-dark(#fdfcf7, #212932);
}
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }
// theme names baked in, zero deps
const NAMES = ["light", "dark"];
export const setTheme = (theme) => { … };
export const getTheme = () => { … };
// re-applies the saved choice on import
export type ThemeName = "light" | "dark";
export declare function setTheme(
theme: ThemeName | "system",
): void;
// setTheme("drak") → compile error
<link rel="stylesheet" href="varth.css">
.card { background: var(--th-surface); }
import { setTheme } from "./varth.js";
setTheme("dark"); // typed, persisted
<!-- force a theme on any subtree -->
<aside data-theme="dark">…</aside>
Live tokens
Everything below uses var(--th-*). Flip the theme —
registered @property tokens transition smoothly.
Card
surface, text, muted, border, shadow, radius
Always light
data-theme="light" on this card only — scoped
theming, no wrapper components.
Always dark
data-theme="dark" — light-dark() flips
per subtree via color-scheme.
Ramp: one color in, palette out
ramps: { brand: { base } } emits
--th-brand-1..10 as
oklch(from var(--th-brand) …) — the browser derives the
shades. Pick a new base: