# Color

> One system: the Material Design 3 theme tokens.

## Theme tokens

Color in Expressive is the Material Design 3 Expressive roles — CSS custom properties on `:root` named `--md-sys-color-*`, exposed as utility classes like `.primary` and `.on-surface-text`. There are 26 standard roles in six groups (primary, secondary, tertiary, error, surface, outline), plus optional add-ons for fixed accents and the surface container scale.

The 2014 Material palette that shipped with earlier versions (`.red`, `.blue.lighten-2`, `colorFunc()`) has been removed. It generated 532 utility classes — 18% of the stylesheet — expressing a design opinion this framework does not hold, and it did not follow the page theme. Replace a palette class with the role it was standing in for: `.red` → `.error`, `.blue` → `.primary`, `.green` → `.tertiary`, and pair each fill with its `on-*` text class.

Token values follow the page theme. The docs site sets `<html theme="light">`, `theme="dark"`, or `theme="auto"`. Auto (and omitting the attribute) follows the OS.

A background class sets `background-color`. Append `-text` for `color`. Pair roles as the spec intends so contrast stays at least 3:1 when the theme flips:

-   **Primary** — most prominent components: FAB, filled buttons, active states.
-   **Secondary** — less prominent components: filter chips, selected nav pills.
-   **Tertiary** — contrasting accents that balance primary and secondary.
-   **Error** — error states, not decoration.
-   **Surface** — page backgrounds. **Surface container** — cards, sheets, dialogs, menus.
-   **On-\*** — text and icons on that fill. Never use a container role for text.
-   **Variant** — a lower-emphasis alternative to its pair (`outline-variant`, `on-surface-variant`).

Example: a filled button is `primary` + `on-primary`; a tonal button is `secondary-container` + `on-secondary-container`.

.primary

.on-primary

.primary-container

.on-primary-container

.secondary-container

.tertiary

.error

.surface-container

```
<div class="primary on-primary-text">.primary</div>
<span class="on-surface-text">.on-surface-text</span>
```

Every role name is a background class. The same name plus `-text` is the foreground class (`.on-surface-text`). Prefer the `on-*` text class on its paired fill.

In Sass, consume the token directly. Do not write `rgba(var(--md-sys-color-primary), 0.06)` — the tokens hold hex colors, so that form is invalid. Mix with transparency instead:

```
.my-tint {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
}

.my-overlay {
  background-color: color-mix(in oklab, var(--md-sys-color-primary) 6%, transparent);
}
```

Mix `in oklab`, not `in srgb`. sRGB interpolation dips in lightness through the midtones, so the same percentage reads muddier on some hues than others; OKLab is perceptually uniform, so a 16% state layer looks like 16% everywhere.

## Every role

A role is a job, not a color: `error` means "this went wrong", and what that looks like is the theme's business. Each swatch uses its paired `on-*` color for the label.

### Primary

.primary

.primary-container

.primary-fixed

.primary-fixed-dim

### Secondary

.secondary

.secondary-container

.secondary-fixed

.secondary-fixed-dim

### Tertiary

.tertiary

.tertiary-container

.tertiary-fixed

.tertiary-fixed-dim

### Error

.error

.error-container

### Surface

.surface-dim

.surface

.surface-bright

.surface-container-lowest

.surface-container-low

.surface-container

.surface-container-high

.surface-container-highest

### Outline

.outline

.outline-variant

### On and inverse

.on-surface

.on-surface-variant

.inverse-surface

.inverse-on-surface

.inverse-primary

### Overlay

.scrim

.shadow

.surface-tint

`scrim` is the opaque neutral the wash behind a modal surface is mixed from, not the wash itself. That is `--md-comp-scrim-color` — the role at 32%, defined once at the root and consumed by dialogs, both sheets, the navigation drawer and the modal navigation rail. Override it at the root to move every scrim, or set it on one surface to dim just that one — `::backdrop` inherits from the element it belongs to. The mix resolves at the root, so overriding `--md-sys-color-scrim` on a subtree does not reach a scrim below it; override `--md-comp-scrim-color` instead.

`background`, `on-background`, and `surface-variant` stay as aliases of `surface` / `on-surface` / the old neutral-variant well so existing pages do not break. New work should use the surface container scale.

## Sass

Do not `@extend` these classes across files — that is how the old stylesheet lost control of cascade order. In a component partial, read the token directly.

```
.my-panel {
  background-color: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface);
}
```

There is no Sass color function any more. `colorFunc()` and the `$colors` map were removed with the palette — a Sass function resolves at build time, which cannot follow a theme the user switches at runtime. The custom property can.
