guide 1 of 3
What are Client Hints?
A protocol answer to a twenty-five-year-old mistake: the browser identification header that grew until nobody could parse it, lied to everyone, and identified users better than cookies.
The problem: one header, three jobs, zero structure
Since the 1990s, every HTTP request has carried a User-Agent string like this:
User-Agent: Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Mobile Safari/537.36 Count the lies: this Chrome claims to be Mozilla, WebKit, KHTML, Gecko and Safari. That is sediment from decades of sites sniffing for the "right" browser and browsers faking ancestry to get the good version of pages. On top of the lies, the string did three incompatible jobs at once:
- Adaptation: servers picking layouts, formats, download binaries;
- Analytics: counting browsers, OS versions, device classes;
- Identification: the same detail that powers the first two jobs makes the string a potent passive fingerprint: exact build, device model and OS build narrow millions of users to a handful, on every request, with no consent surface at all.
You cannot fix the third job without breaking the first two, unless you split the header into pieces and put the revealing pieces behind a request mechanism. That split is Client Hints.
The design: cheap facts flow, costly facts are requested
Client hints sort everything the old string revealed into two tiers. Low-entropy hints travel on every request from a Chromium browser with no setup, and they reveal roughly what a shop window reveals:
Sec-CH-UA: "Chromium";v="126", "Not/A)Brand";v="8", "Google Chrome";v="126"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Linux" High-entropy hints (exact versions, CPU architecture, device model, OS build) only arrive after your server names them in an Accept-CH response header. The browser remembers the grant per origin and attaches the hints to subsequent requests. The negotiation is three moves long, and the live capture on the home page shows your browser performing it against this very site:
The deeper subtleties of move 2 (persistence, the first-request gap, Critical-CH, third-party delegation) get their own page: Accept-CH & Critical-CH.
Not just the user agent
The same machinery carries three more families beyond browser identity:
- Preference hints: dark mode and reduced motion as request headers, so server-rendered HTML is correct on the first paint;
- Device hints: pixel ratio, viewport, RAM bucket, for server-side responsive images and payload budgets;
- Network hints: measured connection quality, plus
Save-Data, the one hint that is a direct human request rather than a measurement.
All 22 are catalogued, with your browser's live values, in the reference.
Details that make it trustworthy
- The
Sec-prefix is a guarantee. Headers starting withSec-are forbidden to JavaScript;fetch()cannot set them. ASec-CH-UAvalue was written by the browser itself (or by a non-browser client lying honestly, which is its own signal). - GREASE keeps parsers honest. Browsers inject a fake brand with hostile punctuation (
"Not/A)Brand") into every brand list and shuffle the order, so code that pattern-matches instead of parsing breaks in week one instead of year three. - HTTPS only. Hints beyond the defaults are never granted over plain HTTP.
The honest caveats
Engine support is the big one: client hints ship in every Chromium browser and in nothing else. Firefox and Safari send no Sec-CH-* headers and expose no navigator.userAgentData, with no announced plans to change. Client hints are therefore a progressive enhancement over User-Agent parsing, not a replacement for it, especially while the UA string itself is being reduced under everyone's feet.
The privacy improvement is a budget, not a ban. A site that requests every high-entropy hint reconstructs most of the old fingerprint. It does so visibly, auditable in DevTools, but it reconstructs the fingerprint nonetheless. The mechanism's real contribution is making that request observable and attributable instead of ambient.