skip to content

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%

answer

  1. fractional points, fractional pixels
  2. layout rounded at mount, from the root
  3. values that bypass layout
  4. Math.round(size x ratio) / ratio
  5. 8.4 becomes 8.33 at 3x

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.

solid answer

~50 s

Points times the pixel ratio is often fractional: 8.4 points at 3x is 25.2 pixels, and a view edge between pixels looks soft or makes a thin line vanish or double. React Native handles this for normal layout: JavaScript and Yoga work with arbitrary-precision numbers, and only when positions and sizes are set on native views are they rounded, relative to the root so errors do not accumulate. `PixelRatio.roundToNearestPixel(size)` returns `Math.round(size * ratio) / ratio`, the nearest point value that is a whole number of pixels, 8.33 for 8.4 at 3x. You need it for values that do not go through that final layout rounding but must line up with it: an offset or snap interval you compute in JavaScript, a translate transform, or a size you compare with measured layout. The rule from the docs: never mix rounded and unrounded values.

code

tsx · 22 lines
tsx
import { PixelRatio, StyleSheet, View } from 'react-native';

const STEP_HEIGHT = 72.4;

export function StepMarker({ index }: { index: number }) {
  const offset = PixelRatio.roundToNearestPixel(index * STEP_HEIGHT);
  const onePixel = 1 / PixelRatio.get();
  return (
    <View
      style={[styles.marker, { width: onePixel, transform: [{ translateY: offset }] }]}
    />
  );
}

const styles = StyleSheet.create({
  marker: {
    position: 'absolute',
    left: 0,
    height: STEP_HEIGHT,
    backgroundColor: '#8a8f98',
  },
});

go deeper

for a junior

Recall that points times the ratio can be fractional and that React Native rounds normal layout for you.

for a middle

Explain where React Native rounds, what roundToNearestPixel returns, and which JavaScript-computed values still need it.

for a senior

Diagnose blurry or uneven thin lines by tracing values that bypass layout, and enforce a single rounding point per computed value.

for a principal

Set review guidance for pixel-sensitive UI, such as dividers, timelines and animated highlights, and decide which devices and ratios the test matrix must cover.

## Why fractions are a problem Style numbers are density-independent points, and physical pixels are points times `PixelRatio.get()`. The product is frequently fractional. On a 3x phone, 8.4 points is 25.2 pixels; a width of one third of 391 points is about 130.33 points, or 391 pixels exactly, but add a margin of 0.5 and you are between pixels again. A display cannot light a fraction of a pixel. If an edge falls between pixels, the platform can blend it across neighbours, which makes it look **blurry**, or a one-pixel line can **vanish or come out twice as thick**. In a recipe app's step timeline, a thin vertical line between steps is exactly the kind of detail that shows this. ## What React Native already rounds React Native's own docs describe the policy: - Everything in JavaScript and inside the layout engine uses **arbitrary-precision numbers**. - Rounding happens only when the final **position and dimensions are set on the native view**. - Rounding is done **relative to the root**, not the parent, so small errors do not add up as views nest. So for ordinary layout, views sized and placed by Yoga, you do not round by hand. Rounding early would actually hurt, because a rounded parent and unrounded children accumulate error. ## What PixelRatio offers | Method | Returns | Example at 3x | |---|---|---| | `PixelRatio.get()` | The pixel ratio | `3` | | `PixelRatio.roundToNearestPixel(points)` | `Math.round(points * ratio) / ratio`, a point value on the pixel grid | `8.4` becomes `8.33` (25 pixels) | | `PixelRatio.getPixelSizeForLayoutSize(points)` | `Math.round(points * ratio)`, an integer pixel count | `8.4` becomes `25` | `roundToNearestPixel` stays in points, so its result can go straight back into a style or a calculation. ## When to round yourself Round values that **bypass the final layout rounding** but must match what it produced: 1. **Offsets you compute in JavaScript**, such as a snap interval or a scroll-to position derived from item sizes, which must land exactly where Yoga placed the items. 2. **Transforms**, such as a `translateY` that slides a marker to the current recipe step; a transform is applied after layout, so its value is not part of the layout rounding described above. 3. **Comparisons with measured layout**, for example checking whether a computed height equals what `onLayout` reported; compare rounded values, or allow a tolerance. 4. **Thin lines you size by hand**: `1 / PixelRatio.get()` is exactly one physical pixel on any density. The `StyleSheet` module also exports a ready-made thin-line constant for this. ## A worked example The recipe screen's step marker slides to step `index` with `translateY: index * 72.4`. On a 3x phone: 1. For step 3, the raw offset is 217.2 points, which is 651.6 pixels, not on the grid. 2. `PixelRatio.roundToNearestPixel(217.2)` computes `Math.round(651.6) / 3`, which is 652 / 3, about 217.33 points. 3. The marker now starts exactly on pixel 652, while the step rows laid out by Yoga were rounded to whole pixels too, so the marker and the row edge meet cleanly. ## The one rule: do not mix The docs put it bluntly: never work with rounded and unrounded values at the same time, because the rounding errors accumulate and a one-pixel border may disappear or double. In practice: - Choose **one place** where a computed value is rounded, just before it is applied. - Keep intermediate arithmetic unrounded. - Do not round a parent's size and then derive children from the rounded number while siblings use the raw one. ## Diagnosing a blurry or uneven line - Check whether the value came from JavaScript arithmetic that skipped layout, such as a transform or an animated offset. - Log the value times `PixelRatio.get()`; if it is not an integer, that is the fractional edge. - Round the final value with `roundToNearestPixel` and compare on both a 2x and a 3x device, since a value can be clean on one and fractional on the other.

  • Why does React Native round relative to the root rather than to each parent?
    If each view were rounded relative to its parent, every level of nesting could add up to half a pixel of error, and deep trees would drift visibly out of line. Rounding each view's absolute position from the root means every edge lands on the pixel nearest its true position, whatever the nesting.
  • Why can a value be crisp on one phone and blurry on another?
    Whether points times the ratio is a whole number depends on the ratio. 8.5 points is 17 pixels at 2x but 25.5 at 3x. Test pixel-sensitive details on devices with different ratios, and round with `roundToNearestPixel`, which uses the current device's ratio.

It is like cutting tiles for a floor: you measure everything precisely and cut once at the end against the same wall, rather than rounding each tile as you go and ending up a centimetre off at the far side.

saying these in an interview costs you the question

  • React Native passes fractional layout values to native views unchanged.
  • Round every style value by hand before passing it to layout.
  • Rounding each view relative to its parent is the safest approach.
  • roundToNearestPixel returns an integer number of pixels.
  • A width of 1 is always one physical pixel.