skip to content

Units & Pixel Density

Sizes are density-independent points that PixelRatio maps to pixels, and useWindowDimensions re-renders on rotation or a fold. Interviewers probe blurry borders and layouts stuck at launch size.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In React Native, what unit is a style value such as width: 120, and how does PixelRatio map it to physical pixels?

level: juniorimportance: must knowfreq 66%

answer

  1. no units in a style object
  2. density-independent points, dp on Android
  3. PixelRatio.get() is the scale
  4. pixels = points x scale
  5. getPixelSizeForLayoutSize rounds to integers

basics

~20 s

Style lengths are unitless density-independent points (dp on Android). PixelRatio.get() returns the device's scale, so width: 120 covers 120 x scale physical pixels: 360 on a 3x screen. getPixelSizeForLayoutSize does that conversion, rounded to an integer.

solid answer

~40 s

React Native style numbers have no unit: they are **density-independent points** (iOS points, Android dp), so `width: 120` is roughly the same physical size on every phone. The device's density is `PixelRatio.get()`, which is the `scale` of the window: 1 on low-density Android screens, 1.5 or 2 on mid-density ones, 3 on many current phones. Physical pixels are points times that scale, so `width: 120` spans 360 pixels at 3x. `PixelRatio.getPixelSizeForLayoutSize(120)` performs the conversion and rounds to a whole number, which is what you use when asking a server for an image of the right resolution. There is no `px`, `rem` or viewport unit; percentages are the only other length form.

code

tsx · 13 lines
tsx
import { Image, PixelRatio } from 'react-native';

const THUMB = 120;

export function RecipeThumb({ baseUrl }: { baseUrl: string }) {
  const px = PixelRatio.getPixelSizeForLayoutSize(THUMB);
  return (
    <Image
      source={{ uri: `${baseUrl}?w=${px}&h=${px}` }}
      style={{ width: THUMB, height: THUMB }}
    />
  );
}

go deeper

for a junior

Recall that style numbers are unitless points, that PixelRatio.get() is the density, and that pixels equal points times that ratio.

for a middle

Explain what getPixelSizeForLayoutSize and roundToNearestPixel return, and why layout stays in points while image requests use pixels.

for a senior

Show where density matters in production: remote image sizing, memory in long lists, and why density must never drive layout breakpoints.

for a principal

Set a policy for image delivery across densities, trading bandwidth, memory and sharpness, and decide what the image service must support.

## Points, not pixels Every length in a React Native style, `width`, `height`, `margin`, `borderWidth`, `fontSize`, is a plain number with no unit. That number is a **density-independent point**: iOS calls it a point, Android calls it a dp. The screen's physical pixels are a separate grid underneath. The point exists so that a layout keeps roughly the same physical size across devices. A recipe thumbnail with `width: 120` is about the same size on a small low-density Android phone and on a high-density flagship, even though the second one packs far more pixels into it. The mapping from points to physical units is not exact across all devices, but the difference is rarely noticeable. ## PixelRatio.get and the scale factor `PixelRatio.get()` returns the device's **pixel density**: how many physical pixels one point spans along each axis. In the source it is simply the `scale` field of `Dimensions.get('window')`. | `PixelRatio.get()` | Typical devices | |---|---| | `1` | Low-density (mdpi) Android screens | | `1.5` | hdpi Android screens | | `2` | xhdpi Android screens, many older iPhones | | `3` | xxhdpi Android screens, iPhones such as the X and 11 Pro | | `3.5` | Some xxxhdpi Android screens | So the conversion is always: **physical pixels = points x `PixelRatio.get()`**. At 3x, `width: 120` spans 360 physical pixels. ## One thumbnail across densities The same 120-point recipe thumbnail covers very different pixel counts: | `PixelRatio.get()` | Physical pixels for `width: 120` | Image to request | |---|---|---| | `1` | 120 | 120 x 120 | | `1.5` | 180 | 180 x 180 | | `2` | 240 | 240 x 240 | | `3` | 360 | 360 x 360 | The layout is identical on all four: the thumbnail takes the same share of the screen. Only the number of pixels available to draw it changes, which is why density matters for image sharpness and memory, but not for layout decisions. ## Converting sizes in code `PixelRatio` offers two helpers built on that multiplication: 1. `PixelRatio.getPixelSizeForLayoutSize(points)` returns `Math.round(points * PixelRatio.get())`, so it is **guaranteed to be an integer** number of pixels. At 3x, `getPixelSizeForLayoutSize(33.5)` is 101. 2. `PixelRatio.roundToNearestPixel(points)` goes the other way: it returns the nearest point value that lands on a whole number of pixels, for example 8.33 instead of 8.4 at 3x. ## Requesting a correctly sized remote image The most common use of `getPixelSizeForLayoutSize` is fetching remote images at the right resolution. A recipe list shows 120-point thumbnails. Requesting a 120-pixel image would look soft on a 3x phone, because it is stretched across 360 pixels; requesting a 1,200-pixel image wastes bandwidth and memory. Multiply the layout size by the ratio and ask the image service for that: ```tsx const px = PixelRatio.getPixelSizeForLayoutSize(120); const uri = `${baseUrl}?w=${px}&h=${px}`; ``` The `Image` still gets `width: 120, height: 120` in its style, because layout is always in points. Only the **source request** is in pixels. ## Units that do not exist - `'120px'`, `'8rem'`, `'10em'`, `'30vw'` and `'50vh'` are not valid lengths; styles take numbers or percentage strings such as `'50%'`. - There is no automatic "1 CSS pixel equals 1 point" translation: you are always working in points. - `fontSize` is also in points, and the user's text-size setting multiplies it further; that font scale is reported separately. ## Common mistakes - **Multiplying layout sizes by the ratio**: `width: 120 * PixelRatio.get()` makes the view three times too big at 3x. Layout stays in points; only pixel-level requests use pixels. - **Assuming one density per platform**: Android devices ship at many densities, and iOS devices at 2x and 3x. - **Using the ratio for breakpoints**: density says nothing about how much room the app has; a large tablet can have a lower ratio than a small phone. Breakpoints come from the window's width in points. - **Caching the ratio forever**: the value comes from the window metrics, so read it when you need it instead of freezing it at import time. Knowing that the style number is a point and that `PixelRatio.get()` is the bridge to pixels answers most "why is this image blurry" and "why is this view the wrong size" questions.

  • Why not simply request every remote thumbnail at 3x to be safe?
    A 3x image on a 1.5x or 2x device is downscaled after download, so you pay for bandwidth and decoded image memory you never show. In a long recipe list that adds up quickly. Requesting `getPixelSizeForLayoutSize(size)` fetches exactly what the screen can display.
  • Is PixelRatio.get() the same value as useWindowDimensions().scale?
    Yes. `PixelRatio.get()` reads `Dimensions.get('window').scale`, which is the same `scale` field that `useWindowDimensions()` returns. The hook's advantage is that it re-renders the component if the window metrics change; `PixelRatio.get()` just returns the current value when called.

saying these in an interview costs you the question

  • React Native style numbers are physical pixels.
  • You can write width: '120px' or '8rem' in a style.
  • Multiply every layout size by PixelRatio.get() to support high-density screens.
  • All Android devices share a single pixel ratio.
  • A higher pixel ratio means the device has more room for layout.
open as a page

In React Native, why does a layout sized with Dimensions.get at module scope stay stuck at launch size, and how does useWindowDimensions fix it?

level: middleimportance: must knowfreq 62%

basics

~20 s

Dimensions.get returns a snapshot, and a StyleSheet built at module scope captures it once, so rotation, unfolding or split-screen never update it. useWindowDimensions subscribes to Dimensions change events and re-renders the component with the new window size.

open as a page

In React Native, when do you still need PixelRatio.roundToNearestPixel if the framework already snaps layout to the pixel grid?

level: middleimportance: should knowfreq 24%

basics

~20 s

React Native rounds layout positions and sizes to whole pixels when it applies them to native views. roundToNearestPixel is for values you compute and apply outside that path, such as offsets or transforms, so they land on whole pixels and line up.

open as a page

A React Native recipe app should show one pane when a foldable phone is folded and two panes when unfolded; how would you build that switch?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Treat the fold as a window resize: read width from useWindowDimensions at the screen root, pick one or two panes at a width threshold in points, and keep the selected recipe in state above both layouts so switching panes does not lose it.

open as a page

In React Native, what do PixelRatio.getFontScale() and useWindowDimensions().fontScale report, and how should layout respond when the user enlarges text?

level: middleimportance: nice to knowfreq 16%

basics

~20 s

Both report the user's system text-size multiplier, around 1 at the default size and higher when text is enlarged. useWindowDimensions re-renders when it changes, so layout can reflow, for example stacking a two-column ingredient row, instead of clipping.

open as a page