hints / preference

Sec-CH-Prefers-Color-Scheme

Sec-CH-Prefers-Color-Scheme tells the server whether the user prefers light or dark, so the first HTML response can be themed correctly with no flash.

requires Accept-CH Preference
Example
Sec-CH-Prefers-Color-Scheme: dark
JavaScript
matchMedia('(prefers-color-scheme: dark)')
Available since
Chrome 93 (2021)
Engines
Chrome Edge Opera Firefox Safari
References
MDN · Specification

Sec-CH-Prefers-Color-Scheme moves the prefers-color-scheme media query into the request itself, solving a problem CSS cannot: server-rendered theming without a flash. If your dark mode is decided by a class on <body>, only the server can get the very first paint right, and to do that it needs to know the preference before sending a byte of HTML.

Accept-CH: Sec-CH-Prefers-Color-Scheme
Critical-CH: Sec-CH-Prefers-Color-Scheme

Sec-CH-Prefers-Color-Scheme: dark    # or: light

Two format details: the value is an unquoted token (no quotes, unlike the UA string hints), and the possible values are exactly light and dark.

Critical-CH matters here

Notice Critical-CH in the example. On a user’s very first visit the browser hasn’t seen your Accept-CH yet, so the first request carries no preference. That first response is precisely the one you wanted themed. Marking the hint critical makes the browser retry that initial request with the hint attached before rendering anything. One extra round-trip, once per origin, in exchange for never flashing the wrong theme. Details on the Accept-CH page.

Cache accordingly

If the HTML differs by scheme, shared caches must know:

Vary: Sec-CH-Prefers-Color-Scheme

In JavaScript

const dark = matchMedia('(prefers-color-scheme: dark)').matches;

The JS check works in every browser, including Firefox and Safari; only the header is Chromium-only. The standard pattern is therefore: use the header when present for a correct first paint, and keep the matchMedia listener for live theme switches and non-Chromium traffic.