Screen, color depth, and device-pixel-ratio as fingerprint entropy
Open a console on any page and type screen.width, screen.height, screen.colorDepth, window.devicePixelRatio. Four numbers come back with no permission prompt, no user gesture, no visible sign that anything was read. On the laptop this is being written on the answer is 1512, 982, 30, 2. Those four values describe a panel, a color pipeline, and an OS scaling factor, and together they put the machine into a fairly small bucket of all the machines on the web. Not a unique bucket. But a small one, and one that combines cheaply with everything else a tracker already has.
The interesting part is what happens when you move the window. Drag it to an external monitor and devicePixelRatio flips. Maximize it and availHeight jumps by the height of the taskbar. Hit Ctrl-plus twice and the pixel ratio drifts to some ugly float. Each of those numbers is supposed to describe hardware, but every one of them is contaminated by software state, and that contamination is exactly what makes the values both a tracking signal and, when they stop making physical sense, a bot tell. This post is about those four numbers and the cluster around them: where the entropy comes from, how much there is, why it is unstable, and how a detector reads the same fields you would use to look human.
A roadmap
We start with the API surface, the handful of screen and window properties a script reads and what each one is specified to return. Then the 2010 measurement that first put a number on how much these fields are worth, and why that number is smaller than people assume but still matters. After that the physics: how zoom, OS display scaling, and multi-monitor setups inject devicePixelRatio into the mix and turn a stable hardware fact into a moving target. Then the instability problem from the tracker’s side, which is the reason production fingerprinters treat screen size with suspicion. Then the bot-detection flip, where the same fields are read for internal consistency rather than identity. We finish with the defenses the browsers shipped, and where the whole vector sits in 2026.
The fields a script reads
The screen object is old. It predates almost every fingerprinting concern, it shipped so that a page could lay itself out against the display, and it exposes a small set of integers. The current definitions live in the CSSOM View Module, which standardized behavior that browsers had already implemented for years.
screen.width and screen.height return the size of the “Web-exposed screen area.” On a single-monitor desktop that is usually the panel’s resolution in CSS pixels. screen.availWidth and screen.availHeight return the “Web-exposed available screen area,” which is the same rectangle minus space taken by OS furniture, the Windows taskbar, the macOS menu bar and Dock, a Linux panel. screen.colorDepth and screen.pixelDepth return the bits of color per pixel, and the spec says both should return the same value for compatibility. Then window.devicePixelRatio, defined in the same document, returns the ratio of physical device pixels to CSS pixels.
That last one carries an algorithm worth reading, because it is the source of most of the trouble later. The spec says to take the size of a CSS pixel at the current page zoom, take the size of a device pixel, and divide. The phrase “at the current page zoom” is doing a lot of work. It means devicePixelRatio is not a pure hardware property. It folds zoom into the number by design.
There are neighbors worth naming, because collectors read them in the same pass. window.innerWidth and innerHeight give the viewport. window.outerWidth and outerHeight give the whole browser window including chrome. window.screenX and screenY give the window’s position on the desktop. None of those are on the screen object, but a fingerprinter reads the lot and looks at the relationships between them, which is where the consistency checks later come from.
2010: putting a number on it
The first rigorous measurement of how much any of this is worth came from the EFF’s Panopticlick experiment, written up by Peter Eckersley and presented at PETS in 2010. The study collected fingerprints from a large volunteer population, on the order of half a million browsers, and computed the Shannon entropy each component contributed. The numbers are old now. The lesson is not.
Panopticlick grouped screen resolution and color depth into a single variable it called “video,” recorded as a string like 1280x800x24, the resolution followed by the bit depth. Across the dataset that variable carried a mean of 4.83 bits of surprisal. To put that in context, the same study measured the User-Agent string at 10.0 bits, the plugin list at 15.4, and the font list at 13.9. So screen-and-color was a middle-weight signal, well behind the heavy hitters, but far from nothing. At 4.83 bits it splits the population into roughly twenty-eight groups, and it is cheap, fast, and requires no plugin.
The reason 4.83 is lower than people expect is that resolutions cluster hard. A huge fraction of the world runs one of a dozen common panel sizes, and color depth at the time was almost always 24. Most users land in a handful of big buckets. The entropy lives in the long tail: the ultrawide at an odd resolution, the high-DPI laptop, the vertical secondary monitor, the user who scaled their desktop to 125 percent and shifted every derived number by a non-obvious amount. That tail is small as a fraction of users and enormous as a fraction of distinct values.
Panopticlick’s headline was that 94.2 percent of browsers carrying Flash or Java were instantaneously unique, with the full fingerprint reaching at least 18.1 bits. Screen was one ingredient of that, and a stable-looking one, until you watch it over time. Eckersley noted that fingerprints drift, and called out connecting an external monitor as one concrete cause of a resolution changing mid-session. That single aside foreshadowed the entire instability problem that production fingerprinters spend real effort on today.
A later large-scale study, AmIUnique, collected 118,934 fingerprints and found 89.4 percent unique. By then canvas and WebGL had entered the mix and dominated the high-entropy slots, which pushed screen further down the ranking. The point held across both datasets: screen size and color depth are reliable, low-cost, low-to-medium entropy, and most useful in combination rather than alone.
Where devicePixelRatio comes from
Color depth has gone quiet over the years. Almost every device reports 24, and browsers increasingly normalize anything unusual, so as a discriminator it has faded toward a near-constant. The action moved to devicePixelRatio, and to understand why it matters you have to separate three things that all push the same number around.
The first is the panel. A standard 96-DPI monitor gives a devicePixelRatio of 1. A HiDPI or Retina display gives 2. Modern phones often report 3 or more. That part is genuine hardware and is what the property was meant to express.
The second is operating-system display scaling, the “make everything bigger” slider in the OS. On Windows, set display scaling to 125 percent and the browser sees a devicePixelRatio of 1.25. Set it to 150 and you get 1.5. macOS exposes a coarser set of scaled resolutions that land on similar fractional ratios. This is where the ugly floats come from, and where a surprising amount of entropy hides, because the combination of a specific panel resolution with a specific OS scaling factor is rarer than either alone.
The third is page zoom. The CSSOM spec is explicit that zoom is folded into the ratio. MDN’s reference puts it plainly: when a page is zoomed in, the size of a CSS pixel increases, and so devicePixelRatio increases. Ctrl-plus once and the number moves. Pinch-zoom, the two-finger gesture on a touchscreen or trackpad, does not change it, because that magnifies the page without changing the CSS pixel size. So the single float a script reads is the product of hardware density, an OS slider, and a per-tab zoom level, three independent inputs collapsed into one number with no way to cleanly factor them back apart.
The combination is what carries information. A devicePixelRatio of exactly 2 on a 1512-wide screen is the dead-common MacBook signature. A ratio of 1.25 with a 1536-wide screen is a Windows laptop at 125 percent scaling, also common. But a ratio of 1.1, or 2.625, or some other artifact of zoom layered on scaling, is rare, and rare is what a fingerprint wants. A user fiddling with zoom to make text readable is, without knowing it, sharpening their own fingerprint.
Multi-monitor makes it worse for the user and better for the tracker. Moving a window between displays with different densities changes devicePixelRatio live, and the platform gives pages a way to watch for exactly that. The MDN pattern builds a media query string from the current ratio and listens for the change event:
const mq = matchMedia(`(resolution: ${devicePixelRatio}dppx)`);mq.addEventListener("change", onRatioChange);That is a legitimate API for redrawing canvas art at the right crispness when the window crosses monitors. It is also a free notification, to anyone listening, that this user has a multi-monitor setup with mixed densities, which is itself a discriminating fact about the machine. The feature and the side channel are the same feature.
The instability problem
From the tracker’s side, screen entropy comes with a catch that the other big fingerprint vectors mostly avoid. Canvas output is stable across a session and usually across weeks. The TLS ClientHello is stable until the browser updates. Screen dimensions are not stable at all. They wobble during a single session, for reasons that have nothing to do with the user changing machines.
availWidth and availHeight are the obvious offenders. Maximizing a window, entering and leaving fullscreen, auto-hiding the taskbar, plugging in a second monitor, all of these change the available area without changing the hardware. The FingerprintJS team hit this directly. Their public issue tracker has a string of reports about fingerprints flipping because available resolution shifted by a few pixels during the day, or because a window moved between monitors, or because macOS fullscreen reported a different available rectangle than windowed mode. Their resolution was telling: the screen resolution source in the open-source library reads screen.width and screen.height, deliberately not the avail* values, and sorts the two numbers in descending order so that a rotated tablet reporting [800, 1280] and [1280, 800] hashes the same. Orientation should not split one device into two identities.
Even that was not enough. The same library carries a note that in private mode on Safari 17, the reported window resolution becomes the document size rather than the screen size, so the function returns nothing on that browser rather than feed an unstable value into the hash. The lesson production fingerprinters learned is that screen size is best used as a weak, normalized signal, never a primary key, and color depth is nearly worthless because it almost never varies. The entropy is real but slippery, and a fingerprint built to lean on it would rot.
This is the recurring tension in the whole field. The most stable signals, like the GPU string behind WebGL fingerprinting or the rasterizer behind canvas, are the ones browsers work hardest to lock down. The signals that are easy to read, like screen size, are the ones that drift. A fingerprint that wants both stability and entropy has to combine many weak, noisy fields and accept that any one of them might flip on a given day. Screen is a textbook weak-but-cheap field: you take it, you normalize it, you weight it low, you do not bet the identity on it.
The bot-tell flip
Everything above treats these numbers as a tracking signal, measuring how rare your particular machine is. A bot detector reads the same fields for a different purpose. It does not care whether your screen is rare. It cares whether your four numbers are internally consistent and consistent with everything else the browser claims. The same screen object that tracks a human catches an automated one, by failing arithmetic rather than by being unusual.
The classic tell is the headless default. A headless Chrome instance launched without an explicit window size historically reported a screen that did not match a real desktop, and in some configurations reported availability dimensions identical to the full screen because there was no taskbar, no chrome, nothing subtracted. On a real Windows machine availHeight is almost always a bit less than height, because the taskbar eats a strip. A browser where availHeight === height exactly, on a platform that claims to be Windows, is mildly suspicious on its own and very suspicious in combination. The same logic catches a virtual display in a container that reports a flat, too-clean rectangle.
The richer checks are arithmetic across the related properties. outerWidth should be at least innerWidth, because the window chrome has nonzero width, and usually outerHeight exceeds innerHeight by the height of the toolbar. screen.width should be at least availWidth. innerWidth times devicePixelRatio should land near a sensible physical width. A real browser satisfies all of these because the OS and the window manager produce them together. A spoofed environment that overrides one property in isolation, say, patching devicePixelRatio to 2 to fake a Retina display while leaving the screen dimensions at a 96-DPI laptop’s values, breaks the arithmetic. The detector does not need to know your true configuration. It only needs to find one equation that does not balance.
This is why naive spoofing makes things worse. Anti-detect browsers that let an operator set a custom screen resolution have to override the whole cluster coherently, screen, avail*, inner*, outer*, devicePixelRatio, and the matching CSS media query results, or they leave a seam. Get one wrong and you have manufactured an inconsistency that a real browser could never produce, which is a stronger signal than the rare-but-real resolution you were trying to hide. The same dynamic shows up across the navigator object and the rest of the JavaScript runtime: the cheapest fields to fake are the easiest to fake wrong.
There is a behavioral layer on top. Because devicePixelRatio and availHeight change when a real user zooms, maximizes, or moves a window, a session that holds these values perfectly constant across a long interaction is, in aggregate, slightly more bot-like than one that shows the small natural drift a human produces. No single observation proves anything. Detectors work in aggregate, and a too-stable screen is one of many small weights on the scale.
What the browsers did about it
Defenses against screen-based entropy split into two philosophies, and both shipped.
The Tor Browser took the bucketing approach. Rather than reveal the true window size, it starts with a content window rounded to a multiple of 200 by 100 pixels, deliberately herding users into a small number of identical-looking buckets. That works until someone maximizes or goes fullscreen, at which point the real size leaks back. To cover that case Tor adopted letterboxing, a technique developed by Mozilla, which pads the content area with neutral margins so the page sees a bucketed size even when the window is maximized. The visible gray borders are the cost of the privacy. The whole point is that many users report the same screen size, so the field carries near-zero entropy.
Firefox generalized the same machinery under privacy.resistFingerprinting. With the pref on, the browser reports the viewport as the screen, decoupling the value from the real monitor, and applies letterboxing to step window dimensions to common multiples. The mode also normalizes devicePixelRatio. The earliest version of this had a famous own-goal, documented in Mozilla’s own bug tracker: an early implementation reported the live viewport as the screen, which for an unusual real resolution produced an unusual reported one, so a user with a 3440x1440 ultrawide ended up with a stranger, more unique value than if nothing had been done. The fix was to step to common buckets rather than echo the viewport, which is the rounding-to-the-crowd idea again.
Mainstream browsers without a privacy mode took the quieter path of normalizing the most abusable specifics. Color depth is the clearest case. The CSSOM spec now says a user agent should return 24 when the real depth is unknown or being withheld for privacy, which is why almost everything reports 24 and the field has gone nearly flat as a discriminator. WebKit’s fingerprinting notes, which catalog the screen object as a known vector and cite the same 4.83-bit figure from the 2010 study, describe restricting reported window sizes to a few predetermined values and rounding screen width to the nearest band, the Tor approach pulled into the engine. Safari clamps devicePixelRatio toward integers and, as the FingerprintJS source documents, changed what window resolution means in private mode, which is why a careful fingerprinter now special-cases that browser.
The result in 2026 is a split population. A privacy-configured browser reports a bucketed, letterboxed, normalized screen that carries almost no entropy by design, and that very flatness is itself detectable, since a real consumer machine rarely lands on a perfectly round resolution with devicePixelRatio exactly 1 and no taskbar offset. Everyone else reports their true cluster, which still carries the few bits the 2010 study measured, give or take the color-depth field going quiet. Both populations are legible. One because it is unusual, the other because it is suspiciously typical.
Closing
Screen, color depth, and device-pixel-ratio are the unglamorous middle of the fingerprint. They were never the high-entropy stars, the four-point-eight bits the EFF measured in 2010 sat well behind fonts and plugins, and color depth has since collapsed toward a constant. But they earn their place by being free, by being orthogonal to the rendering-based signals, and by carrying a second payload that the flashier vectors do not. They describe relationships, the small arithmetic between width and available width, between viewport and window, between physical and CSS pixels, that a real operating system maintains automatically and a spoof has to reconstruct by hand.
That second payload is the durable one. Browsers can letterbox the window size to nothing and clamp color depth to 24, and they have. What they cannot do is make the relationships between the values arbitrary, because real software produces real arithmetic, and any environment that overrides one number without the others leaves a seam a detector can find. The most interesting thing about devicePixelRatio is not how rare your particular value is. It is that the single float quietly records three independent facts, your panel, your OS scaling, your zoom, and the moment someone fakes one of the three, the float stops matching the screen dimensions that should have moved with it. The number that was supposed to describe a piece of glass ends up describing whether you are telling the truth.
Sources & further reading
- Eckersley, P. (2010), How Unique Is Your Web Browser? — the EFF Panopticlick study; measures the “video” variable (resolution plus color depth) at 4.83 bits of mean surprisal and the full fingerprint at 18.1 bits, with 94.2% of Flash/Java browsers instantaneously unique.
- W3C CSS Working Group (2024), CSSOM View Module Level 1 — the normative definitions and algorithms for devicePixelRatio, screen.width/height, availWidth/availHeight, and colorDepth, including the “return 24 for privacy” note.
- MDN Web Docs, Window: devicePixelRatio property — confirms zoom folds into the ratio, pinch-zoom does not, and documents the matchMedia resolution pattern for detecting monitor changes.
- Laperdrix, P., Rudametkin, W., Baudry, B. (2016), Beauty and the Beast: Diverting Modern Web Browsers to Build Unique Browser Fingerprints — the AmIUnique study; 118,934 fingerprints, 89.4% unique, with screen resolution as a stable mid-entropy attribute behind canvas and WebGL.
- Mowery, K., Shacham, H. (2012), Pixel Perfect: Fingerprinting Canvas in HTML5 — the canvas paper; relevant here as the orthogonal high-entropy vector that pushed screen down the ranking.
- Tor Project, Fingerprinting protections — describes rounding the content window to multiples of 200x100 px and the letterboxing fallback for maximized windows.
- WebKit, Fingerprinting — engine-level notes citing the screen object as a vector, the 4.83-bit figure, and the restrict-to-predetermined-sizes mitigation.
- FingerprintJS, screen_resolution.ts source — production handling: reads screen.width/height, sorts descending for orientation stability, and disables the signal on Safari 17 private mode.
- Mozilla, Bug 1560816: screen size under resistFingerprinting — documents the own-goal where echoing the viewport as the screen made an ultrawide more unique, and the move to bucketed dimensions.
- Mozilla, Bug 1330882: set new windows to rounded dimensions — the resistFingerprinting work to round new-window dimensions, ported from the Tor uplift.
Further reading
Canvas fingerprinting: how a single toDataURL call identifies a device
Traces how rendering text and shapes to an HTML5 canvas and hashing the toDataURL output yields a stable per-device value, the GPU, driver and font causes behind the variation, the 2012 origin, and how much entropy it really carries.
·22 min readWebGL fingerprinting: the renderer string, precision, and shader quirks
A primary-source reference on WebGL fingerprinting: the UNMASKED_RENDERER and UNMASKED_VENDOR strings, supported extensions, shader precision formats, rendered-image hashing, and the browser mitigations that bucket or hide them.
·24 min readAudioContext fingerprinting: the OscillatorNode signature explained
Traces how rendering an oscillator through OfflineAudioContext and a DynamicsCompressor produces a stable per-device float, the floating-point and FFT causes behind the variation, the 2016 origin, and how much entropy it really carries.
·18 min read