The Header That Carries Your Dark Mode
SnapFrom Team · · 6 min read
Set your operating system to dark mode and a fact about you becomes available to servers, before any cookie is set and before any script runs that a privacy tool could catch. It can arrive as a line in the HTTP request:
GET / HTTP/1.1
Host: example.com
Sec-CH-Prefers-Color-Scheme: "dark"
Sec-CH-Prefers-Color-Scheme is one of a small family of request headers that report your appearance and accessibility settings. MDN calls its value “the user’s preference for light or dark color themes”, set “through an operating system setting (for example, light or dark mode) or a user agent setting”[2]. The header exists for a real reason, and the same request that turns it on can turn on several more.
Why a server wants your theme at request time
A site that supports dark mode has a sequencing problem. The browser knows your preference immediately, but the server that builds the page does not. If the server guesses light and inlines light-theme CSS, a dark-mode reader gets a white flash before the page corrects itself. The specification names this outcome directly: sites “by all means want to avoid a flash of inaccurate color theme”[1].
The fix the working group settled on is to move the preference into the request. High-traffic sites “that inline CSS for performance reasons need to know about the preferred color scheme … ideally at request time, so that the initial HTML payload already has the right CSS inlined”[1]. Google’s own writeup gives Google Search as the example and the same rationale[7]. With the header in hand, the server sends the correct theme in the first byte of HTML and there is nothing to correct.
It is off until the site asks
None of these headers is sent by default. MDN’s client hints guide splits hints into a low-entropy set that goes out unprompted, which is just Save-Data and three headers naming the browser, and everything else[6]. Sec-CH-Prefers-Color-Scheme is “a high entropy hint so the site needs to opt into receiving it by sending an appropriate Accept-CH response header”[2]. Accept-CH is a response header that lists “which client hint headers should be included by the client in subsequent requests”[4]. Once the browser has seen it, the named hints ride along on later requests to that origin.
Requesting the front pages of thirteen large sites with a plain client, on 9 September 2026, showed how few sites use it:
$ curl -sS -D - -o /dev/null https://www.google.com/
accept-ch: Sec-CH-Prefers-Color-Scheme
accept-ch: Downlink
accept-ch: RTT
accept-ch: Sec-CH-UA-Platform
...
vary: Accept-Encoding
$ curl -sS -D - -o /dev/null https://www.instagram.com/
accept-ch: viewport-width,dpr,Sec-CH-Prefers-Color-Scheme,
Sec-CH-UA-Full-Version-List,Sec-CH-UA-Platform-Version,Sec-CH-UA-Model
$ curl -sS -D - -o /dev/null https://www.youtube.com/
accept-ch: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, ... Sec-CH-Viewport-Width,
Sec-CH-DPR, Device-Memory
Google and Instagram ask for the color-scheme hint on the first response. YouTube sends Accept-CH but asks only for browser, viewport and memory hints. The other ten sites in the set, among them Facebook, Wikipedia, Amazon and the New York Times, sent no Accept-CH at all. The header is a Google and Meta habit more than a web-wide one so far.
Google’s response has a wrinkle. It asks for Sec-CH-Prefers-Color-Scheme but does not list it in Vary, so a shared cache will not keep a separate light and dark copy of the page keyed on it. MDN’s guidance is that a hint which changes the response “should generally also be included in the affected response’s Vary header”[6], and also that doing so for a fast-changing value “effectively makes the resource uncacheable”[6]. Reconciling the two is left to the site, and Google has chosen not to vary its cached HTML on your theme.
Critical-CH can make one navigation into two requests
Accept-CH has a gap: the very first request to a site goes out before any Accept-CH has been seen, so it carries none of the opt-in hints. A server that needs the theme to build the first page still does not have it.
Critical-CH closes the gap. It marks a hint as one the server cannot proceed without, and the browser’s obligation is specific: “user agents receiving a response with Critical-CH must check if the indicated critical headers were sent in the original request. If not, the user agent will retry the request along with the critical headers rather than render the page”[5]. MDN’s worked example for Sec-CH-Prefers-Reduced-Motion shows the sequence: the response comes back with Accept-CH, Vary and Critical-CH, and then “the client automatically retries the request”, the second time carrying Sec-CH-Prefers-Reduced-Motion: "reduce"[3]. That retry is invisible from the reader’s side. The address bar shows one navigation while the server has had two chances to read a preference, and built the page from the second.
None of the thirteen sites captured above sent Critical-CH for the color-scheme hint, so today the earliest any of them learns your theme is the second request you make to them. The mechanism to move it to the first request is one response header away.
The same line gets the accessibility settings
Sec-CH-Prefers-Color-Scheme is the one sites actually use, but it is not alone. The specification defines five headers in the family, and lists each as a “high-entropy client hint”[1]:
Sec-CH-Prefers-Reduced-Motion, valuesno-preferenceorreduceSec-CH-Prefers-Reduced-Transparency, same valuesSec-CH-Prefers-Contrast, valuesno-preference,less,moreorcustomSec-CH-Forced-Colors, valuesnoneoractiveSec-CH-Prefers-Color-Scheme, valueslightordark
The last one correlates weakly with anything. Dark mode is a widespread taste. The other four are different. reduce for motion “indicates that a user has enabled the setting on their device for reduced motion”, a setting that exists because animations “can trigger discomfort for those with vestibular motion disorders”[8]. prefers-contrast: more means “user has notified the system that they prefer an interface that has a higher level of contrast”, and user agents “rely on the settings provided by the operating system” for it[9]. forced-colors: active means a reader is running a limited system colour palette, which Windows High Contrast mode sets. A theme is a taste. The other settings are not, and a server that collects the set is reading a signal that correlates with vision and motion sensitivity.
A site enables them the same way it enables the theme hint, by naming the token in Accept-CH. Nothing in the mechanism treats the accessibility hints differently from the colour one. No site in the capture asks for them yet, but the distance between “asks for dark mode” and “asks for the reduced-motion and contrast settings too” is one edit to a header list.
Where that leaves the reader
The specification’s privacy note heads off the obvious objection. The data “is otherwise attainable and/or detectable using JavaScript and CSS”, so moving it to the request “does not grant the server access to any previously inaccessible information”[1]. A script running matchMedia("(prefers-reduced-motion: reduce)") reads the same bit, and that is true.
What changes is when and how quietly. The header arrives before the response that would carry any script, so it is available to the server ahead of anything the reader could inspect, extension could rewrite, or content blocker could intercept. Once a site has sent Accept-CH, the hint is attached to later requests automatically, with no prompt and nothing on the page to show it happened[4]. Critical-CH can pull it forward into a silent retry of the first navigation[5].
The levers that exist are coarse. The headers are “only available to HTTPS secure contexts” and follow the top-level origin by default, with a “default allowlist of 'self'” that keeps them off cross-site subresource requests unless the first party delegates them[1]. A user agent “may intentionally omit the Sec-CH-Prefers-Color-Scheme header to preserve user privacy”[2], though Chrome sends it when asked. Firefox and Safari implement none of the family, so using either opts you out of it entirely[7]. If you use dark mode in Chrome, Google and Instagram already receive that bit on the second request you make to them, and the channel that carries it is built to carry more.
Sources
- User Preference Media Features Client Hints Headers (Draft Community Group Report)
- Web Incubator Community Group (WICG), accessed
Supports: The specification 'introduces a set of user preference media features client hints headers like Sec-CH-Prefers-Color-Scheme, which notify the server of user preferences that will meaningfully alter the requested resource'. 'These client hints will commonly also be used as critical client hints via the Critical-CH header.' Motivation: 'High-traffic sites like search engines or news sites that wish to honor user preference media features like prefers-color-scheme and that inline CSS for performance reasons need to know about the preferred color scheme ... ideally at request time, so that the initial HTML payload already has the right CSS inlined', and 'specifically for prefers-color-scheme, sites by all means want to avoid a flash of inaccurate color theme'. It defines five headers, each described as a 'high-entropy client hint': Sec-CH-Prefers-Reduced-Motion ('no-preference' / 'reduce'), Sec-CH-Prefers-Reduced-Transparency ('no-preference' / 'reduce'), Sec-CH-Prefers-Contrast ('no-preference' / 'less' / 'more' / 'custom'), Sec-CH-Forced-Colors ('none' / 'active') and Sec-CH-Prefers-Color-Scheme ('light' / 'dark'). Each is a policy-controlled feature 'which has a default allowlist of 'self''. The usage example shows a response carrying 'Accept-CH: Sec-CH-Prefers-Color-Scheme, Sec-CH-Prefers-Contrast', 'Vary: Sec-CH-Prefers-Color-Scheme' and 'Critical-CH: Sec-CH-Prefers-Color-Scheme', after which 'the client then retries the request' with 'Sec-CH-Prefers-Color-Scheme: "dark"'. Privacy Considerations, in full: 'The User Preference Media Features Client Hint header exposes data at the HTTP request level that is otherwise attainable and/or detectable using JavaScript and CSS as defined in [MEDIA-QUERIES-LEVEL-5]. By providing this data at the request level, the server gains the ability to customize the response based on the user's preferences. Importantly, it does not grant the server access to any previously inaccessible information.' Security Considerations: the header 'will only be available to HTTPS secure contexts'.
- Sec-CH-Prefers-Color-Scheme header
- MDN Web Docs, Mozilla, accessed
Supports: The Sec-CH-Prefers-Color-Scheme request header 'is a media feature client hint which provides the user's preference for light or dark color themes'. 'A user indicates their preference through an operating system setting (for example, light or dark mode) or a user agent setting.' 'Sec-CH-Prefers-Color-Scheme is a high entropy hint so the site needs to opt into receiving it by sending an appropriate Accept-CH response header.' 'A user agent may intentionally omit the Sec-CH-Prefers-Color-Scheme header to preserve user privacy since the user's preference could, in theory, be used for fingerprinting.' The value is 'a string indicating the user agent's preference for dark or light content: "light" or "dark"'. The example response carries Accept-CH, Vary and Critical-CH all naming Sec-CH-Prefers-Color-Scheme, and the following request carries 'Sec-CH-Prefers-Color-Scheme: "dark"'. MDN marks the feature 'Limited availability' with the note that it 'is not Baseline because it does not work in some of the most widely-used browsers', and 'This feature is available only in secure contexts (HTTPS)'.
- Sec-CH-Prefers-Reduced-Motion header
- MDN Web Docs, Mozilla, accessed
Supports: The Sec-CH-Prefers-Reduced-Motion request header 'is a user agent client hint which indicates the user agent's preference for animations to be displayed with reduced motion'. 'If a server signals to a client via the Accept-CH header that it accepts Sec-CH-Prefers-Reduced-Motion, the client can then respond with this header to indicate the user's preference for reduced motion.' The value is 'no-preference' or 'reduce'. The worked example shows the server responding with Accept-CH, Vary and Critical-CH for Sec-CH-Prefers-Reduced-Motion, after which 'the client automatically retries the request (due to Critical-CH being specified above), telling the server via Sec-CH-Prefers-Reduced-Motion that it has a user preference for reduced-motion animations', and the retry request carries 'Sec-CH-Prefers-Reduced-Motion: "reduce"'. MDN marks it 'Limited availability' and 'not Baseline', available 'only in secure contexts (HTTPS)'.
- Accept-CH header
- MDN Web Docs, Mozilla, accessed
Supports: The Accept-CH response header 'may be set by a server to specify which client hint headers should be included by the client in subsequent requests'. 'To ensure client hints are sent reliably, the Accept-CH header should be persisted for all secure requests.' The example pairs it with Vary: 'The Vary header indicates which values were used to vary the response based on the accepted client hints.' 'This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.'
- Critical-CH header
- MDN Web Docs, Mozilla, accessed
Supports: The Critical-CH response header 'is used along with Accept-CH to identify the accepted client hints that are critical'. 'User agents receiving a response with Critical-CH must check if the indicated critical headers were sent in the original request. If not, the user agent will retry the request along with the critical headers rather than render the page.' 'This approach ensures that client preferences set using critical client hints are always used, even if not included in the first request, or following server configuration changes.' 'Each header listed in the Critical-CH header should also be present in the Accept-CH and Vary headers.' The worked example carries Accept-CH / Vary / Critical-CH for Sec-CH-Prefers-Reduced-Motion, followed by an automatic retry request that now includes 'Sec-CH-Prefers-Reduced-Motion: "reduce"'.
- HTTP Client Hints
- MDN Web Docs, Mozilla, accessed
Supports: Client hints are 'a set of HTTP request header fields that a server can proactively request from a client to get information about the device, network, user, and user-agent-specific preferences'. 'The low entropy hints are those that don't give away much information that might be used to fingerprint a user. They may be sent by default on every client request, irrespective of the server Accept-CH response header, depending on the permission policy.' The low entropy set is 'Save-Data', 'Sec-CH-UA', 'Sec-CH-UA-Mobile' and 'Sec-CH-UA-Platform'. The guide's 'User preference media features client hints' section states these hints 'allow a server to vary responses based on a user agent's preferences for CSS media features such as color scheme or reduced motion' and names Sec-CH-Prefers-Reduced-Motion and Sec-CH-Prefers-Color-Scheme. On caching: 'Client hints that determine which resources are sent in responses should generally also be included in the affected response's Vary header. This ensures that a different resource is cached for every different value of the hint header.' And: 'You may prefer to omit specifying Vary or use some other strategy for client hint headers where the value changes a lot, as this effectively makes the resource uncacheable.'
- User preference media features client hints headers
- Chrome for Developers, Google, accessed
Supports: The article's motivation is that 'high-traffic sites like Google Search that wish to honor user preference media features like prefers-color-scheme and that inline CSS for performance reasons, need to know about the preferred color scheme ... ideally at request time', and that sites 'by all means want to avoid a flash of inaccurate color theme'. A server opts in with an Accept-CH response header such as 'Accept-CH: Sec-CH-Prefers-Color-Scheme, Sec-CH-Prefers-Contrast', and 'Critical Client Hints are Client Hints which meaningfully change the resulting resource. Such a resource should be fetched consistently across page loads (including the initial page load) to avoid jarring user-visible switches.' Support: Sec-CH-Prefers-Color-Scheme shipped in Chromium 93, Sec-CH-Prefers-Reduced-Motion in Chromium 108, and 'Other vendors' feedback, namely WebKit's and Mozilla's, is pending.'
- prefers-reduced-motion (CSS media feature)
- MDN Web Docs, Mozilla, accessed
Supports: The reduce value 'indicates that a user has enabled the setting on their device for reduced motion'; 'the setting is used to convey to the browser on the device that the user prefers an interface that removes, reduces, or replaces motion-based animations'. 'Such animations can trigger discomfort for those with vestibular motion disorders. Animations such as scaling or panning large objects can be vestibular motion triggers.' no-preference 'indicates that a user has made no preference known on the device'.
- prefers-contrast (CSS media feature)
- MDN Web Docs, Mozilla, accessed
Supports: prefers-contrast 'is used to detect whether the user has requested the web content to be presented with a lower or higher contrast'. more 'indicates that user has notified the system that they prefer an interface that has a higher level of contrast'; less is the opposite; custom matches a specific user-chosen palette. 'Various operating systems do support such preferences and user agents are likely to rely on the settings provided by the operating system.'