Theming

One stylesheet of CSS custom properties drives every component — colors, control sizes, radii and motion.

Theming is CSS custom properties, nothing else. No provider, no JS config, no build step. Override a variable and every component that reads it follows, instantly, including ones you copied months ago.

Light and dark

The theme is an attribute on <html>:

<html data-theme="dark">   <!-- default -->
<html data-theme="light">

tokens.css defines the dark palette on :root and overrides it under :root[data-theme='light']. Switching is one attribute write:

document.documentElement.dataset.theme = 'light';
localStorage.setItem('theme', 'light');

Set it before first paint (an inline script in <head>) so the page never flashes the wrong palette.

The token groups

Surfaces and text

TokenRole
--bgPage background
--bg-cardCard / panel surface
--bg-elevatedPopovers, menus, dialogs
--bg-inputField interiors
--borderDefault hairline
--border-hover · --border-strongHover and emphasis
--textPrimary text
--text-secondaryBody copy
--text-mutedLabels, hints, captions

Accent and status

--accent and --accent-contrast drive the primary action color. --success, --danger, --warn and --info back the tone prop that most components accept.

:root {
  --accent: #7c5cff;
  --accent-contrast: #ffffff;
}

That single change repaints every primary button, focus ring and active state in the app.

The control scale

This is the one that keeps a row of mixed components aligned. Height, radius, font size and horizontal padding all come from the same three-step scale:

smmdlg
Height--ctrl-h-sm 32px--ctrl-h-md 40px--ctrl-h-lg 48px
Radius--ctrl-r-sm 10px--ctrl-r-md 12px--ctrl-r-lg 14px
Font--ctrl-fs-sm 13px--ctrl-fs-md 14px--ctrl-fs-lg 15px
Padding X--ctrl-px-sm 12px--ctrl-px-md 14px--ctrl-px-lg 18px

A button, an input and a select with size="md" line up pixel for pixel because all three read --ctrl-h-md. Want a squarer product? Change the three radii once:

:root {
  --ctrl-r-sm: 4px;
  --ctrl-r-md: 6px;
  --ctrl-r-lg: 8px;
}

Motion

TokenDefault
--dur-fast200ms
--dur-mid260ms
--dur-slow320ms
--ease-outcubic-bezier(.22, 1, .36, 1)
--ease-springcubic-bezier(.34, 1.56, .64, 1)

Under prefers-reduced-motion: reduce, tokens.css zeroes the durations for you. JS-driven motion has its own guard — see Effects & motion.

Effects tint

--fx-tint is the light color used by the proximity glow and the press ripple, written as space-separated RGB because the effects compose it with alpha:

:root { --fx-tint: 255 255 255; }              /* dark theme  */
:root[data-theme='light'] { --fx-tint: 0 0 0; } /* light theme */

Scoping a theme to one subtree

Tokens cascade, so a section can carry its own palette:

<section style="--accent: #22c55e; --bg-card: #04140b;">
  <VsButton label="Deploy" />
</section>

Nothing else in the page changes.

Per-component tokens

Some components expose their own namespaced variables for fine control — --btn-primary-bg, --inp-border, --sel-*, and so on. They all fall back to the global tokens, so you only reach for them when you want that one component to differ:

.checkout .btn--primary {
  --btn-primary-bg: #111;
  --btn-primary-fg: #fff;
}