AniUI Academy

Platform Differences: Platform.OS and Platform.select

How to branch behavior and styling by platform with Platform.OS and Platform.select, and how platform-specific file extensions let Metro pick the right file automatically.

9 min read

You already know that React Native renders real native components instead of HTML. The flip side of that is real too: iOS and Android are two different operating systems with genuinely different conventions, and sometimes your code needs to know which one it's running on. Platform.OS and Platform.select are the two tools for that, and a platform-specific file extension is a third, quieter way to do the same thing at the file level.

Platform.OS: the simplest branch

Platform.OS is a plain string, read once, telling you which platform your JS is currently running on:

import { Platform } from "react-native";
 
if (Platform.OS === "ios") {
  console.log("Running on iOS");
} else if (Platform.OS === "android") {
  console.log("Running on Android");
}

It's exactly "ios" or "android" on a real device — nothing fancier, no version number, no device model. If the app also targets React Native Web, Platform.OS can additionally come back as "web", which matters if the codebase genuinely spans phone and browser, though most React Native apps only ever see the first two values.

Reach for a plain if/else on Platform.OS when the difference is a single conditional value or a small behavioral branch: a status bar style, a slightly different label, whether to show a control at all.

Platform.select: declarative branching for values and styles

Once a component is picking between more than one or two platform-specific values — especially inside a style object — Platform.select reads better than a chain of conditionals, because it describes "here's the whole set of options for this one thing" in a single expression:

import { Platform, StyleSheet } from "react-native";
 
const styles = StyleSheet.create({
  card: {
    ...Platform.select({
      ios: {
        shadowColor: "#000",
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.15,
        shadowRadius: 4,
      },
      android: {
        elevation: 4,
      },
      default: {},
    }),
  },
});

Platform.select takes an object keyed by platform name (plus an optional default for anything not explicitly listed) and returns whichever value matches the platform actually running. It works for entire style objects, like above, or for a single value:

const headerHeight = Platform.select({ ios: 44, android: 56, default: 48 });

The shadow example is a real, motivating case rather than a toy one: iOS and Android genuinely use different mechanisms to draw the same visual idea — a raised card with a soft shadow beneath it. iOS reads a handful of shadow* props (color, offset, opacity, radius) on any view. Android ignores all of those and instead reads a single elevation number, deriving the shadow from Material Design's lighting model. Set only the iOS props and Android renders a perfectly flat card; set only elevation and iOS renders nothing at all. Platform.select is the tool for handing each platform exactly the props it understands, in one expression.

Platform-specific file extensions

The third mechanism works at the file system level, not inside your code at all. Name two files Button.ios.js and Button.android.js in the same directory, and an ordinary import of ./Button resolves differently depending on which platform Metro is building for:

// Button.ios.js
export default function Button() {
  // an iOS-flavored button implementation
}
 
// Button.android.js
export default function Button() {
  // an Android-flavored button implementation
}
 
// SomeScreen.js
import Button from "./Button"; // resolves to the matching file, per platform

This is a real, stable Metro convention — not a hack, not a workaround. It's the right tool specifically when an entire component's implementation genuinely differs by platform, rather than just a handful of style values or props, since it lets you write two completely separate implementations without a single Platform.OS check cluttering either one.

Choosing between the three

A rough rule of thumb: a single conditional value reads best as a plain Platform.OS check; a small set of platform-specific values (especially inside a style object) reads best as Platform.select; and an entire component whose implementation diverges meaningfully by platform is the signal to split it into .ios. / .android. files instead of littering one file with branches. None of these are mutually exclusive — a real app typically uses all three, at different scales, often in the same screen.

Two more real, long-standing platform differences worth knowing as motivation for reaching for any of this: Android's hardware or gesture back action has no direct iOS equivalent, which is why navigation libraries expose Android-specific hooks for intercepting it; and status bar, safe-area, and keyboard-avoiding behavior also differ enough between the platforms that "write it once, ignore the platform" quietly breaks on one of the two the moment an app gets non-trivial.

What to remember

  • Platform.OS is "ios" or "android" (or "web", if targeting React Native Web) — use it for simple conditionals.
  • Platform.select({ ios, android, default }) declaratively picks a value or whole style object per platform, and reads better than a chain of conditionals once there's more than one branch.
  • Button.ios.js / Button.android.js lets Metro resolve a plain ./Button import to the right file automatically, per platform — a real bundler convention, not a hack.
  • iOS shadows (shadow* props) and Android elevation are genuinely different mechanisms for the same visual effect — a concrete, common reason to reach for Platform.select.
  • Android's back button/gesture has no iOS equivalent, which is why navigation libraries expose platform-aware handling specifically for it.

Check yourself

4 questions · pass 3/4 to unlock Permissions and Native Modules

up to 50
  1. 1.Which value does Platform.OS return when your code is running in an iOS app?

  2. 2.A Button.ios.js and a Button.android.js both exist in the same folder, and elsewhere in the app there's a plain import Button from "./Button". What decides which file actually gets used?

  3. 3.What's the correct way to give a view an iOS shadow and an Android elevation without one platform silently ignoring the other's styling?

  4. 4.Why can't Android's hardware/gesture back button be handled by writing the exact same code you'd use for iOS's edge-swipe-back gesture?

4 left to answer