> ## Documentation Index
> Fetch the complete documentation index at: https://substrate.docs.unknowncreatives.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Modes as Presets Over a Continuous Preference Vector

> Substrate has no parallel token sets. It has seven continuous preference axes, and modes like dark or highContrast are named points on that continuum, re-solved by the runtime.

Most design systems ship modes as parallel artifacts: a light token set, a dark token set, and a switch that swaps one for the other. Substrate does not work that way. It models user context as a **continuous preference vector** with seven axes, and a "mode" is nothing more than a **named preset** — a convenient pair of coordinates on that continuum. Everything between the presets is reachable, and the values at every point are solved, not looked up.

This page covers the preference vector, the five shipped presets, how the runtime applies them, and what the no-JS fallback does and does not cover. For the underlying mental model, see [Core Concepts](/core-concepts#modes).

***

## The preference vector

Every axis is an independent, continuous scalar (`cvd` being a small struct rather than a number). These are the real defaults from `defaultPreferences()`:

| Axis              | Range                   | Default                         | What it scales                      |
| ----------------- | ----------------------- | ------------------------------- | ----------------------------------- |
| `scheme`          | 0 (light) → 1 (dark)    | `0`                             | Position along the light/dark track |
| `contrastFactor`  | 0.75 → 1.5              | `1`                             | Every APCA Lc target                |
| `densityFactor`   | 0.8 → 1.3               | `1`                             | Spacing, sizing, border radius      |
| `typeScaleFactor` | 0.9 → 1.4               | `1`                             | The type scale ratio                |
| `motionFactor`    | 0 (reduced) → 1 (full)  | `0.75`                          | Transition and animation duration   |
| `warmth`          | 0 (neutral) → 1 (amber) | `0`                             | Night-Shift-style warm shift        |
| `cvd`             | `{ type, severity }`    | `{ type: 'none', severity: 0 }` | Color vision compensation           |

Because the axes are independent, they compose freely. A user who wants a dark interface with elevated contrast, reduced motion, and deutan compensation is not choosing a fourth "mode" — they are setting four numbers.

<Note>
  There is no theme attribute in Substrate. `scheme` is a continuous variable, not a string; the runtime writes `data-brand` on the root element; and `data-mode` is a **composable mode list** (intent roles, component roles and states, preset-level and material tokens) — a preset token applies property deltas, it does not move the scheme axis. See [Applying a mode](#applying-a-mode) below.
</Note>

***

## The five presets

Named modes ship in the engine's scheme-preset table (`src/kernel/system/preferences.ts`). Each one sets exactly two axes — `scheme` and `contrastFactor` — and leaves the rest of the vector untouched:

| Preset             | `scheme` | `contrastFactor` | What it is                                                                                                                                                                                                              |
| ------------------ | -------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `light`            | `0`      | `1.0`            | The light end with standard contrast. Surfaces sit near white, and the solver produces dark foregrounds against them.                                                                                                   |
| `dark`             | `1`      | `1.0`            | The dark end. APCA polarity flips and the solver computes light-on-dark values; intents that declare a scheme track can shift hue or chroma here.                                                                       |
| `dimmed`           | `0.65`   | `0.95`           | A **mid-track** position with slightly relaxed contrast. Dimmed is *lighter* than dark, not darker — it sits between the endpoints and is sampled continuously by the solver rather than being a recolor of either end. |
| `highContrast`     | `0`      | `1.3`            | The light end with every Lc target multiplied — the foreground target moves from Lc 75 to roughly Lc 97.5. Hue and chroma are unchanged, so the brand still looks like itself.                                          |
| `darkHighContrast` | `1`      | `1.3`            | The dark end with the same 1.3× multiplier, for users who want a dark interface *and* elevated contrast.                                                                                                                |

The keys are camelCase. There is no `high-contrast` and no `high-contrast-dark`.

Applying a preset is a two-field assignment; nothing about the other axes changes:

```ts theme={null}
import { defaultPreferences } from '@substrate/engine';
import MarkupOptInExample from '/snippets/markup-optin-example.mdx';

const prefs = defaultPreferences();
prefs.scheme = 1;             // darkHighContrast
prefs.contrastFactor = 1.3;
```

The preset table is a convenience, not a gate. Assigning the same two numbers directly is exactly equivalent, which is why an arbitrary position such as `scheme: 0.42` is just as valid as a named one.

<Warning>
  `dimmed` is the one preset with no baked native artifact. Because it sits mid-track, it can only be produced by sampling the track continuously through the kernel. Light, dark, `highContrast`, and `darkHighContrast` each have a static Swift and Compose file; dimmed does not. See [iOS / Swift](/platforms/ios-swift) and [Android / Kotlin](/platforms/android-kotlin).
</Warning>

***

## Applying a mode

On the web, changing a mode means changing the preference vector and re-running the solver. The runtime writes the solved primitives back to CSS custom properties, and the cascade updates:

```ts theme={null}
import {
  syncPrefsToCssVars, updateAllVars, defaultPreferences, BRAND_REGISTRY,
} from '@substrate/engine';

const brand = BRAND_REGISTRY[0];
const prefs = defaultPreferences();

prefs.scheme = 1;            // move to the dark end
prefs.contrastFactor = 1.3;  // elevate every Lc target

syncPrefsToCssVars(prefs);   // writes --scheme, --contrast-factor, --density, …
updateAllVars(brand, prefs); // re-solves APCA, writes the per-intent --ucs-* primitives
```

Nothing about that call sequence is specific to a preset — an arbitrary `scheme` of `0.42` works identically. Full setup lives in the [Quickstart](/quickstart) and the [Runtime & Imports](/integration) page.

Elements opt into the resulting cascade with `data-ucs`, and pick an intent role with `data-mode`:

<MarkupOptInExample />

`data-mode` is matched with `~=` against a space-separated token list, so an element can carry several mode tokens at once — intent roles, component roles and states, brand preset levels, materials. Tokens apply property effects; the scheme axis itself stays on the preference vector. The full token vocabulary is on [Markup Opt-In](/markup).

***

## The no-JS floor

Substrate bakes a static snapshot into each brand's CSS so a server-rendered page paints accessibly before hydration. That snapshot is a **2×2 matrix** — light/dark × normal/more contrast — emitted on `[data-brand="<slug>"]` and gated by media queries:

```css theme={null}
[data-brand="acme"] { /* light, contrastFactor 1.0 */ }

@media (prefers-color-scheme: dark) {
  [data-brand="acme"] { /* the dark preset */ }
}

@media (prefers-contrast: more) {
  [data-brand="acme"] { /* the highContrast preset */ }
}

@media (prefers-color-scheme: dark) and (prefers-contrast: more) {
  [data-brand="acme"] { /* the darkHighContrast preset */ }
}
```

This is the **accessible floor**, resolved for the page/root surface only. JavaScript is the primary path, and these all require it:

* Continuous `scheme` positions — including `dimmed` — and arbitrary `contrastFactor` values
* CVD compensation, `warmth`, `densityFactor`, and the other continuous axes
* Nested and scoped surfaces, where contrast is re-solved against the local background rather than the page

The runtime also reads OS signals directly: `prefers-reduced-motion` drops `motionFactor` to 0, `prefers-contrast: more` raises `contrastFactor` to 1.3 while `prefers-contrast: less` lowers it to 0.85, and `forced-colors: active` disables CVD compensation so the OS palette wins.

***

<CardGroup cols={2}>
  <Card title="Light & Dark" icon="circle-half-stroke" href="/modes/light-dark">
    How the scheme axis works: track evaluation, polarity flip, and continuous positions between the endpoints.
  </Card>

  <Card title="High Contrast" icon="circle-up" href="/modes/high-contrast">
    `contrastFactor` as a multiplier over the Lc policy, and how the runtime picks it up from the OS.
  </Card>

  <Card title="Color Vision" icon="eye" href="/modes/color-vision">
    The four CVD types, continuous severity, hue redistribution, and the kernel's own caveats.
  </Card>

  <Card title="Runtime & Imports" icon="plug" href="/integration">
    The runtime call sequence and the three import aliases this page's vector drives.
  </Card>

  <Card title="Surface Component" icon="layer-group" href="/surface-component">
    Re-solving contrast locally when surfaces nest inside surfaces.
  </Card>
</CardGroup>
