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.
- 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.