skip to content

In React Native, what do Platform.OS and Platform.select return, and how does Platform.select choose between ios, android, native and default keys?

level: juniorimportance: must knowfreq 70%

answer

  1. a string versus a lookup
  2. 'ios' or 'android', even on iPad
  3. own platform key first
  4. then native, then default
  5. key present beats truthy value

basics

~20 s

Platform.OS is the running platform's name, 'ios' or 'android'. Platform.select(spec) returns the value under that platform's key, otherwise under native, otherwise under default, otherwise undefined; the value can be anything, from a number to a style object.

solid answer

~40 s

`Platform` from `react-native` exposes `Platform.OS`, which is `'ios'` or `'android'` on a phone or tablet, handy for one-off checks. `Platform.select(spec)` returns the most specific match: the current platform's key, then `native`, which covers both iOS and Android, then `default`, and `undefined` if none exist. It tests whether the key is **present**, not whether its value is truthy, so `{ ios: undefined, default: 'blue' }` gives `undefined` on iOS. Values can be anything: numbers, style objects spread into a `StyleSheet.create` entry, functions or components. In a weather app's header I would write one `...Platform.select({ ios: {...}, android: {...} })` block rather than a ternary on every property.

code

tsx · 24 lines
tsx
import { Platform, StyleSheet, Text, View } from 'react-native';

export function ForecastHeader({ city }: { city: string }) {
  return (
    <View style={styles.header}>
      <Text style={styles.title}>{city}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    paddingHorizontal: 16,
    ...Platform.select({
      ios: { height: 96, justifyContent: 'flex-end' as const },
      android: { height: 56, justifyContent: 'center' as const },
      default: { height: 64, justifyContent: 'center' as const },
    }),
  },
  title: {
    fontSize: Platform.select({ ios: 17, default: 20 }),
    textAlign: Platform.OS === 'ios' ? 'center' : 'left',
  },
});

go deeper

for a junior

Recall that Platform.OS is 'ios' or 'android' and that Platform.select returns the platform key's value, then native, then default.

for a middle

Explain the presence check, why a missing default yields undefined, and that select replaces rather than merges values, with the TypeScript overloads that surface it.

for a senior

Show judgment on where select belongs, in StyleSheet.create blocks and small value maps, and mention that Metro inlines static selects so branches cost nothing and do not ship.

for a principal

Set a codebase convention: value-level differences through Platform.select with an explicit default, structural differences elsewhere, so reviewers can see every platform's behaviour.

## The Platform module React Native ships one JavaScript module, `Platform`, imported from `react-native`, that tells your code which operating system it is running on. It is the tool for **small, value-level differences**: a height, a font size, a padding, a choice between two components. Larger structural differences are usually moved into separate per-platform files, which is a different technique. ## Platform.OS for one-off checks `Platform.OS` is a plain string. On an Android phone, tablet or TV it is `'android'`; on an iPhone or iPad it is `'ios'`. There is no separate `'ipados'` value; you ask `Platform.isPad` for that. Typical uses: - a single style value: `height: Platform.OS === 'ios' ? 96 : 56`; - a guard around a platform-only API call; - a conditional render: `{Platform.OS === 'android' && <RefreshHint />}`. Note the trap in a ternary: `Platform.OS === 'ios' ? a : b` gives `b` to **every** platform that is not iOS, not only Android. ## Platform.select and its precedence `Platform.select(spec)` takes an object and returns one of its values. React Native's implementation picks, in this order: 1. the key for the current platform, `ios` or `android`; 2. otherwise the `native` key, which matches iOS and Android alike; 3. otherwise the `default` key; 4. otherwise `undefined`. The check is `key in spec`, a **presence** test. A key that exists with the value `undefined` still wins, and a falsy value such as `0` or `''` is returned as is. | Spec | On iOS | On Android | |---|---|---| | `{ ios: 12, android: 14 }` | `12` | `14` | | `{ ios: 12, native: 16, default: 20 }` | `12` | `16` | | `{ native: 16, default: 20 }` | `16` | `16` | | `{ ios: undefined, default: 20 }` | `undefined` | `20` | | `{ ios: 12 }` | `12` | `undefined` | ## What values can go in Because the value is returned untouched, anything works: - **Style fragments**, spread into a `StyleSheet.create` entry: `...Platform.select({ ios: { paddingTop: 12 }, android: { paddingTop: 8 } })`. The shared properties sit outside the spread, the per-platform ones inside. - **Numbers and strings**, such as a header height or an icon name. - **Functions or components**, for example `Platform.select({ ios: () => PullHintIOS, default: () => PullHint })()`. React Native's docs show this pattern with lazy `require` calls so each platform only loads its own module. `Platform.select` does not merge objects. Whichever key wins, its value replaces the others entirely, so a property you need on every platform must live outside the select or be repeated. ## Choosing between them A ternary on `Platform.OS` reads well for one isolated value. As soon as two or three properties of the same style differ, `Platform.select` keeps each platform's values together, and it forces you to say what happens on a platform you did not name. In a weather app, the header's height, bottom padding and title alignment might all differ between iOS and Android; one select block per style entry shows the whole iOS header and the whole Android header side by side, while the shared horizontal padding stays outside as a plain property. Reach for the ternary for a single number, the select for a group. ## Typing and build-time behaviour React Native's TypeScript types give `Platform.select` two shapes. With a `default` key, or with every platform key present, the result is `T`. Without them, it is `T | undefined`, which is the compiler reminding you that another platform gets nothing. `Platform.select` is not necessarily a runtime call. Metro, React Native's bundler, replaces `Platform.OS` with a string literal for the platform it is building, and replaces a `Platform.select` whose argument is a plain object literal with the winning value. In release builds a constant-folding pass then removes branches that can no longer run. The result is that platform checks cost nothing at runtime and the other platform's branch does not ship, as long as `Platform` is imported from `react-native` and the object has no spreads or computed keys. ## Common mistakes - Expecting `Platform.OS` to be `'ipad'` or `'ipados'` on a tablet. - Treating `Platform.select` like `||`, so that a present but `undefined` platform key falls through to `default`. - Forgetting `default` or `native`, then spreading or reading an `undefined` result on the platform you did not list. - Expecting deep merging between the `native` or `default` value and a platform key's value.

  • When would you use the native key in React Native's Platform.select instead of listing ios and android?
    When iOS and Android share a value and only other targets, such as web through React Native Web, need something else: `Platform.select({ native: 16, default: 20 })`. It avoids repeating the same value under two keys. A specific `ios` or `android` key still wins over `native` if you add one later.
  • Does a Platform.select call in React Native cost anything at runtime?
    Usually not. Metro replaces `Platform.OS` with a string literal and a `Platform.select` over a plain object literal with the winning value while transforming each file, and release builds fold the resulting constant conditions away. That only happens when `Platform` comes from `react-native` and the object has no spreads or computed keys; otherwise the call runs normally, which is still cheap.

Platform.select works like a price list with a members' price, a residents' price and a list price: the cashier charges the most specific line printed for you. If your line exists but was left blank, you get the blank line, not the list price.

saying these in an interview costs you the question

  • Platform.OS returns 'ipad' or 'ipados' on an iPad.
  • Platform.select falls through to default when the platform key's value is undefined.
  • Platform.select deep-merges the default object with the platform object.
  • A Platform.OS === 'ios' ternary's else branch only ever runs on Android.
  • Platform.select can only return style objects.