scheme, that runs from 0 (light) to 1 (dark). Every position in between is valid and produces solved values, which is what makes dimmed (scheme: 0.65) a real mode rather than a hand-authored third palette.
Because there is only one axis and one solve, the two ends cannot drift apart. There is no dark override file to forget to update.
What moves along the axis
Three things change asscheme moves from 0 to 1:
- Surface lightness. The base page surface is derived from the brand at the current position, and elevated surfaces are derived from that base.
- Intent hue and chroma, but only if the intent declares a scheme track. A plain intent keeps constant hue and chroma at every position; a tracked intent interpolates between its stops. Track stops are pinned at continuous positions, so evaluation at
0.65is an interpolation, not a snap to an endpoint. - APCA polarity. Near the light end, the solver searches for a dark foreground against a light surface. Near the dark end it searches for a light foreground. The target Lc is the same magnitude; the direction flips.
Lightness is never authored. An intent declares hue and chroma; the solver owns L. That is why moving along the scheme axis cannot break contrast — each position gets its own binary search for a lightness that hits the target against the surface actually in play.
Moving along the axis
Setscheme and re-sync. There is no attribute to toggle:
What the runtime writes
syncPrefsToCssVars writes the raw axis values to the root element, including --scheme itself, so CSS that composes from continuous scalars reacts immediately. updateAllVars runs the APCA solve and writes the per-intent primitives — five numeric properties per intent:
oklch() values from them with calc(), which is what lets a single set of numbers drive an entire cascade. An element selects which intent’s primitives to use through its data-mode role:
Baked light and dark artifacts
Alongside the runtime path, the pipeline bakes static per-mode files for the moments before JavaScript runs and for native platforms. These hold resolved hex values rather than primitives — fromgenerated/brands/acme/css/tokens.light.gen.css:
tokens.light.gen.css, tokens.dark.gen.css, tokens.highContrast.gen.css, and tokens.darkHighContrast.gen.css. There is no tokens.dimmed.gen.css — a mid-track position has no endpoint to bake, so dimmed exists only through the runtime or a native kernel port.
On top of those, each brand’s CSS carries the no-JS snapshot: the same four presets emitted on [data-brand="<slug>"] under prefers-color-scheme and prefers-contrast media queries. That snapshot covers the page surface at first paint; scoped surfaces and continuous positions need the runtime. See Modes Overview.
Authoring intent across the axis
Most intents need nothing beyond a hue and chroma — the solver handles both ends. When a color needs to shift character between the endpoints, declare a scheme track in the brand YAML:scheme-end is the shorthand for a two-stop track; a full scheme-track list gives you stops at arbitrary positions. Letting chroma rise toward the dark end is a common way to keep a color’s perceived character stable, since colors read as less saturated against dark surfaces. Full grammar in Core Concepts and the Color config reference.
Modes Overview
The full seven-axis preference vector and the five named presets.
High Contrast
The contrast axis that scales the Lc policy independently of scheme.