CSS Pixels vs Device Pixels: Why Your Screen Lies About Its Resolution
Browsers report CSS pixels, not physical pixels — that's why a 2560px laptop says 1440. How devicePixelRatio works and how to check your own.
Open your browser console on a retina MacBook and ask for the screen size: screen.width answers 1440, on a panel the spec sheet calls 2560×1600. Nothing is broken — the browser simply doesn’t speak physical pixels. It speaks CSS pixels, an abstract unit whose whole job is to keep the web usable while hardware density keeps changing.
One pixel, two meanings
- Device (physical) pixel — a real light-emitting element on the panel. A 3840×2160 monitor has 8.3 million of them, at a fixed pitch set by the panel’s PPI.
- CSS pixel (
px) — a reference unit defined by viewing angle, not hardware. The CSS spec anchors it at roughly 1/96th of an inch at arm’s length; the browser decides how many physical pixels one CSS pixel covers.
window.devicePixelRatio is the exchange rate between them: device px = css px × devicePixelRatio. At dpr=2 — iPhones, retina MacBooks, most flagship phones — every CSS pixel is a 2×2 quad of physical pixels. The CSS-pixels tab on the calculator shows your device’s live values.
Why the abstraction exists
When the iPhone 4 doubled its display density in 2010, the web faced a choice: either “1px” means a physical pixel and every page’s text shrinks to unreadable size — or “1px” becomes a virtual unit the browser scales. Browsers chose the virtual unit. It turned out to be one of the web’s most quietly successful decisions:
- Layout survives density. A
font-size: 16pxbutton is roughly the same physical size on a 92-ppi office monitor and a 460-ppi phone. - Sharpness scales up. Text and vectors are rendered at device resolution even though they’re measured in CSS pixels — which is why text on retina screens looks printed rather than blocky.
- Zoom fits naturally. Ctrl-+ to 150%? The browser just redefines devicePixelRatio to 1.5× its base value and re-lays-out. Physical pixels don’t change; the exchange rate does.
The numbers browsers actually report
| API | What it returns | On a 2× display |
|---|---|---|
screen.width/height |
screen size in CSS px | half the physical px |
window.innerWidth/innerHeight |
viewport in CSS px | layout space |
devicePixelRatio |
css→device multiplier | 2 |
element.getBoundingClientRect() |
CSS px | layout space |
canvas.width |
backing-store device px | you must set it yourself |
matchMedia('(resolution: 2dppx)') |
media-query density | matches |
That last detail surprises people: canvas is the exception — it sizes in device pixels for its bitmap while CSS controls its layout box. Forget the ctx.scale(dpr, dpr) dance and a retina canvas draws at half resolution.
What it means in practice
Images. A <img width="300"> at dpr=2 wants a 600px-wide source to look sharp. That’s the whole srcset/sizes mechanism: srcset="a-600.jpg 600w, a-1200.jpg 1200w" or the simpler 1x/2x descriptors. Icon fonts and SVG escape the problem entirely — vectors rasterize at device resolution for free.
Canvas and WebGL. Set the backing buffer to cssSize × dpr (and re-do it on resize or zoom — devicePixelRatio is not constant!). For pixel-perfect work there’s devicePixelContentBoxSize in ResizeObserver, which reports the element’s size in actual device pixels.
Screenshots and design specs. A “1440×900 screenshot” from a retina machine is really 2880×1800 device pixels — which is why screenshots from HiDPI machines look enormous in email. Conversely, design mockups in “px” are always CSS px; a 375px-wide mobile artboard targets iPhone CSS width, not its 1125 physical pixels.
Windows’ fractional scaling. At 125% display scaling, dpr=1.25 and one CSS pixel maps to 1.25 physical ones — fractional coverage that GPU compositing smooths over. It’s also why a 1px hairline border can look slightly soft on Windows but razor-sharp on a 2× Mac.
Try it yourself
The calculator’s CSS-pixels tab reads devicePixelRatio, the reported screen and viewport, and multiplies out the physical pixel estimate — live, so you can watch it change. Drag the window between a 1× and 2× monitor, or hit Ctrl-+, and the number updates: the display never changed, only the translation did.
The CSS pixel is the web’s diplomatic solution: pixels stopped meaning “one dot” the day screens stopped agreeing on how big a dot is. devicePixelRatio is just the ledger keeping both sides honest.
Frequently asked questions
Why does my 2560×1600 MacBook report a 1440×900 screen?
Because macOS presents the display to apps as 'looks like 1440×900' at 2× scaling — each CSS point is painted by a 2×2 block of physical pixels. The panel's real 2560×1600 is used for rendering sharpness, but layout measurements stay in the logical 1440×900 space. devicePixelRatio = 2 tells you the multiplier.
Is devicePixelRatio always 1, 2 or 3?
No — it can be any positive number. Common values: 1 (standard monitors), 1.25 (Windows at 125% scaling), 1.5 (some Windows laptops / Android), 2 (retina Macs, most phones), 2.75–3.5 (high-density Android and iPhone Pro Max-class panels). It also changes with page zoom: zoom to 150% on a dpr-1 screen and it reports 1.5.
Why do my images look blurry on a retina screen?
Because a 400px-wide image element at dpr=2 asks the screen to stretch 400 source pixels over 800 physical ones — each source pixel is doubled, losing sharpness. Serve the element a 800px-wide file (via srcset with 2x) and every physical pixel gets real data.
How do I make a canvas sharp on high-DPI displays?
Set the canvas's backing store to CSS size × devicePixelRatio, then scale the context: canvas.width = cssWidth * dpr; canvas.height = cssHeight * dpr; ctx.scale(dpr, dpr). Keep CSS size separate (style.width = cssWidth + 'px'). Skipping this is the classic reason canvas graphics look soft on retina screens.
Does screen.width give me the real resolution?
No — it returns CSS pixels, the same logical space as layout. To estimate physical pixels multiply by devicePixelRatio (the calculator's CSS-pixels tab does exactly this live). For the authoritative panel resolution you'd need OS-level info, which the web deliberately doesn't expose.
Why did browsers invent CSS pixels instead of using real ones?
The iPhone 4 (2010) doubled pixel density overnight — four times the pixels on the same-size screen. If '1px' in CSS meant one physical pixel, every website's text, borders and layout would have shrunk to half their intended size. The CSS pixel became an abstract unit — 'roughly 1/96th of an inch at reading distance' — letting hardware density double without breaking the web.